From 3b95a962395d62aee0c8133efce3bc863a0332bf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:52:45 +0000 Subject: [PATCH 01/73] chore: sync repo --- .babelrc | 9 - .devcontainer/devcontainer.json | 15 + .github/workflows/ci.yml | 100 + .github/workflows/nodejs.yml | 28 - .github/workflows/npmpublish.yml | 49 - .gitignore | 21 +- .mocharc.json | 5 - .npmignore | 19 - .nycrc | 4 - .prettierignore | 7 + .prettierrc.json | 7 + .stats.yml | 4 + Brewfile | 1 + CHANGELOG.md | 38 - CONTRIBUTING.md | 93 + LICENSE | 201 + LICENSE.txt | 1 - README.md | 1390 +---- SECURITY.md | 27 + api.md | 220 + babel-register.js | 3 - bin/publish-npm | 61 + eslint.config.mjs | 42 + examples/.keep | 4 + index.ts | 681 --- jest.config.ts | 23 + libs/constants/errorMessages.ts | 53 - libs/constants/supportedTransforms.ts | 162 - libs/interfaces/BulkDeleteFiles.ts | 16 - libs/interfaces/CopyFile.ts | 15 - libs/interfaces/CopyFolder.ts | 44 - libs/interfaces/CreateFolder.ts | 11 - libs/interfaces/CustomMetatadaField.ts | 123 - libs/interfaces/FileDetails.ts | 224 - libs/interfaces/FileFormat.ts | 20 - libs/interfaces/FileMetadata.ts | 85 - libs/interfaces/FileType.ts | 9 - libs/interfaces/FileVersion.ts | 21 - libs/interfaces/IKCallback.ts | 4 - libs/interfaces/IKResponse.ts | 10 - libs/interfaces/ImageKitOptions.ts | 9 - libs/interfaces/ListFile.ts | 79 - libs/interfaces/MoveFile.ts | 15 - libs/interfaces/MoveFolder.ts | 40 - libs/interfaces/PurgeCache.ts | 27 - libs/interfaces/Rename.ts | 33 - libs/interfaces/Transformation.ts | 10 - libs/interfaces/UploadOptions.ts | 121 - libs/interfaces/UploadResponse.ts | 83 - libs/interfaces/UrlOptions.ts | 65 - libs/interfaces/index.ts | 79 - libs/interfaces/webhookEvent.ts | 153 - libs/manage/cache.ts | 53 - libs/manage/custom-metadata-field.ts | 116 - libs/manage/file.ts | 699 --- libs/manage/index.ts | 33 - libs/signature/index.ts | 33 - libs/upload/index.ts | 110 - libs/url/builder.ts | 179 - libs/url/index.ts | 18 - package.json | 128 +- packages/mcp-server/README.md | 367 ++ packages/mcp-server/build | 32 + packages/mcp-server/jest.config.ts | 17 + packages/mcp-server/package.json | 85 + .../scripts/postprocess-dist-package-json.cjs | 12 + packages/mcp-server/src/code-tool-paths.cts | 3 + packages/mcp-server/src/code-tool-types.ts | 14 + packages/mcp-server/src/code-tool-worker.ts | 46 + packages/mcp-server/src/code-tool.ts | 145 + packages/mcp-server/src/compat.ts | 483 ++ packages/mcp-server/src/dynamic-tools.ts | 153 + packages/mcp-server/src/filtering.ts | 14 + packages/mcp-server/src/headers.ts | 31 + packages/mcp-server/src/http.ts | 115 + packages/mcp-server/src/index.ts | 108 + packages/mcp-server/src/options.ts | 456 ++ packages/mcp-server/src/server.ts | 180 + packages/mcp-server/src/stdio.ts | 13 + packages/mcp-server/src/tools.ts | 1 + .../origins/create-accounts-origins.ts | 338 ++ .../origins/delete-accounts-origins.ts | 43 + .../accounts/origins/get-accounts-origins.ts | 41 + .../accounts/origins/list-accounts-origins.ts | 35 + .../origins/update-accounts-origins.ts | 345 ++ .../create-accounts-url-endpoints.ts | 103 + .../delete-accounts-url-endpoints.ts | 43 + .../get-accounts-url-endpoints.ts | 49 + .../list-accounts-url-endpoints.ts | 44 + .../update-accounts-url-endpoints.ts | 112 + .../accounts/usage/get-accounts-usage.ts | 56 + .../src/tools/assets/list-assets.ts | 94 + .../beta/v2/files/upload-v2-beta-files.ts | 309 + .../invalidation/create-cache-invalidation.ts | 46 + .../invalidation/get-cache-invalidation.ts | 47 + .../create-custom-metadata-fields.ts | 154 + .../delete-custom-metadata-fields.ts | 47 + .../list-custom-metadata-fields.ts | 48 + .../update-custom-metadata-fields.ts | 150 + .../tools/files/bulk/add-tags-files-bulk.ts | 56 + .../src/tools/files/bulk/delete-files-bulk.ts | 49 + .../files/bulk/remove-ai-tags-files-bulk.ts | 56 + .../files/bulk/remove-tags-files-bulk.ts | 56 + .../mcp-server/src/tools/files/copy-files.ts | 55 + .../src/tools/files/delete-files.ts | 41 + .../mcp-server/src/tools/files/get-files.ts | 47 + .../files/metadata/get-files-metadata.ts | 47 + .../metadata/get-from-url-files-metadata.ts | 48 + .../mcp-server/src/tools/files/move-files.ts | 50 + .../src/tools/files/rename-files.ts | 58 + .../src/tools/files/update-files.ts | 192 + .../src/tools/files/upload-files.ts | 325 ++ .../files/versions/delete-files-versions.ts | 52 + .../files/versions/get-files-versions.ts | 50 + .../files/versions/list-files-versions.ts | 47 + .../files/versions/restore-files-versions.ts | 52 + .../src/tools/folders/copy-folders.ts | 55 + .../src/tools/folders/create-folders.ts | 52 + .../src/tools/folders/delete-folders.ts | 48 + .../src/tools/folders/job/get-folders-job.ts | 47 + .../src/tools/folders/move-folders.ts | 50 + .../src/tools/folders/rename-folders.ts | 56 + packages/mcp-server/src/tools/index.ts | 153 + packages/mcp-server/src/tools/types.ts | 103 + packages/mcp-server/tests/compat.test.ts | 1166 ++++ .../mcp-server/tests/dynamic-tools.test.ts | 185 + packages/mcp-server/tests/options.test.ts | 518 ++ packages/mcp-server/tests/tools.test.ts | 225 + packages/mcp-server/tsc-multi.json | 7 + packages/mcp-server/tsconfig.build.json | 18 + packages/mcp-server/tsconfig.dist-src.json | 11 + packages/mcp-server/tsconfig.json | 37 + packages/mcp-server/yarn.lock | 3606 ++++++++++++ sample/README.md | 19 - sample/index.js | 292 - sample/package.json | 14 - sample/test_image.jpg | Bin 199185 -> 0 bytes scripts/bootstrap | 18 + scripts/build | 57 + scripts/build-all | 5 + scripts/format | 12 + scripts/lint | 21 + scripts/mock | 41 + scripts/publish-packages.ts | 102 + scripts/test | 56 + scripts/utils/attw-report.cjs | 24 + scripts/utils/check-is-in-git-install.sh | 9 + scripts/utils/check-version.cjs | 20 + scripts/utils/fix-index-exports.cjs | 17 + scripts/utils/git-swap.sh | 13 + scripts/utils/make-dist-package-json.cjs | 29 + scripts/utils/postprocess-files.cjs | 94 + scripts/utils/upload-artifact.sh | 25 + src/api-promise.ts | 2 + src/client.ts | 897 +++ src/core/README.md | 3 + src/core/api-promise.ts | 92 + src/core/error.ts | 130 + src/core/resource.ts | 11 + src/core/uploads.ts | 2 + src/error.ts | 2 + src/index.ts | 22 + src/internal/README.md | 3 + src/internal/builtin-types.ts | 93 + src/internal/detect-platform.ts | 196 + src/internal/errors.ts | 33 + src/internal/headers.ts | 97 + src/internal/parse.ts | 50 + src/internal/request-options.ts | 91 + src/internal/shim-types.ts | 26 + src/internal/shims.ts | 107 + src/internal/to-file.ts | 154 + src/internal/types.ts | 95 + src/internal/uploads.ts | 187 + src/internal/utils.ts | 8 + src/internal/utils/base64.ts | 40 + src/internal/utils/bytes.ts | 32 + src/internal/utils/env.ts | 18 + src/internal/utils/log.ts | 126 + src/internal/utils/path.ts | 88 + src/internal/utils/sleep.ts | 3 + src/internal/utils/uuid.ts | 17 + src/internal/utils/values.ts | 105 + src/lib/.keep | 4 + src/resource.ts | 2 + src/resources.ts | 1 + src/resources/accounts.ts | 3 + src/resources/accounts/accounts.ts | 55 + src/resources/accounts/index.ts | 20 + src/resources/accounts/origins.ts | 700 +++ src/resources/accounts/url-endpoints.ts | 298 + src/resources/accounts/usage.ts | 70 + src/resources/assets.ts | 105 + src/resources/beta.ts | 3 + src/resources/beta/beta.ts | 15 + src/resources/beta/index.ts | 4 + src/resources/beta/v2.ts | 3 + src/resources/beta/v2/files.ts | 604 ++ src/resources/beta/v2/index.ts | 4 + src/resources/beta/v2/v2.ts | 19 + src/resources/cache.ts | 3 + src/resources/cache/cache.ts | 25 + src/resources/cache/index.ts | 9 + src/resources/cache/invalidation.ts | 70 + src/resources/custom-metadata-fields.ts | 337 ++ src/resources/files.ts | 3 + src/resources/files/bulk.ts | 171 + src/resources/files/files.ts | 1476 +++++ src/resources/files/index.ts | 38 + src/resources/files/metadata.ts | 52 + src/resources/files/versions.ts | 117 + src/resources/folders.ts | 3 + src/resources/folders/folders.ts | 249 + src/resources/folders/index.ts | 16 + src/resources/folders/job.ts | 47 + src/resources/index.ts | 53 + src/resources/shared.ts | 715 +++ src/resources/webhooks.ts | 302 + src/uploads.ts | 2 + src/version.ts | 1 + test-e2e.sh | 33 - tests/api-resources/accounts/origins.test.ts | 119 + .../accounts/url-endpoints.test.ts | 93 + tests/api-resources/accounts/usage.test.ts | 28 + tests/api-resources/assets.test.ts | 42 + tests/api-resources/beta/v2/files.test.ts | 70 + .../api-resources/cache/invalidation.test.ts | 44 + .../custom-metadata-fields.test.ts | 112 + tests/api-resources/files/bulk.test.ts | 101 + tests/api-resources/files/files.test.ts | 215 + tests/api-resources/files/metadata.test.ts | 40 + tests/api-resources/files/versions.test.ts | 74 + tests/api-resources/folders/folders.test.ts | 122 + tests/api-resources/folders/job.test.ts | 23 + tests/api-resources/webhooks.test.ts | 43 + tests/base64.test.ts | 80 + tests/buildHeaders.test.ts | 88 + tests/cache.js | 186 - tests/custom-metadata-field.js | 392 -- tests/data/index.js | 6 - tests/data/test_image.jpg | Bin 199185 -> 0 bytes tests/e2e/node-js/index.js | 31 - tests/e2e/typescript/index.ts | 30 - tests/e2e/typescript/tsconfig.json | 103 - tests/form.test.ts | 85 + tests/helpers/errors.js | 2 - tests/helpers/spies.js | 11 - tests/index.test.ts | 826 +++ tests/initialization.js | 65 - tests/mediaLibrary.js | 1705 ------ tests/path.test.ts | 462 ++ tests/phash.js | 93 - tests/response-metadata.js | 94 - tests/stringifyQuery.test.ts | 29 + tests/unit.js | 78 - tests/upload.js | 599 -- tests/uploads.test.ts | 107 + tests/url-generation.js | 446 -- tests/webhook-signature.js | 145 - tsc-multi.json | 15 + tsconfig.build.json | 18 + tsconfig.deno.json | 15 + tsconfig.dist-src.json | 11 + tsconfig.json | 110 +- utils/authorization.ts | 16 - utils/hamming-distance.d.ts | 1 - utils/phash.ts | 30 - utils/request.ts | 91 - utils/respond.ts | 11 - utils/transformation.ts | 49 - utils/urlFormatter.ts | 73 - utils/webhook-signature.ts | 103 - yarn.lock | 4991 +++++++++-------- 273 files changed, 27473 insertions(+), 12010 deletions(-) delete mode 100644 .babelrc create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/nodejs.yml delete mode 100644 .github/workflows/npmpublish.yml delete mode 100644 .mocharc.json delete mode 100644 .npmignore delete mode 100644 .nycrc create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .stats.yml create mode 100644 Brewfile delete mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE delete mode 100644 LICENSE.txt create mode 100644 SECURITY.md create mode 100644 api.md delete mode 100644 babel-register.js create mode 100644 bin/publish-npm create mode 100644 eslint.config.mjs create mode 100644 examples/.keep delete mode 100644 index.ts create mode 100644 jest.config.ts delete mode 100644 libs/constants/errorMessages.ts delete mode 100644 libs/constants/supportedTransforms.ts delete mode 100644 libs/interfaces/BulkDeleteFiles.ts delete mode 100644 libs/interfaces/CopyFile.ts delete mode 100644 libs/interfaces/CopyFolder.ts delete mode 100644 libs/interfaces/CreateFolder.ts delete mode 100644 libs/interfaces/CustomMetatadaField.ts delete mode 100644 libs/interfaces/FileDetails.ts delete mode 100644 libs/interfaces/FileFormat.ts delete mode 100644 libs/interfaces/FileMetadata.ts delete mode 100644 libs/interfaces/FileType.ts delete mode 100644 libs/interfaces/FileVersion.ts delete mode 100644 libs/interfaces/IKCallback.ts delete mode 100644 libs/interfaces/IKResponse.ts delete mode 100644 libs/interfaces/ImageKitOptions.ts delete mode 100644 libs/interfaces/ListFile.ts delete mode 100644 libs/interfaces/MoveFile.ts delete mode 100644 libs/interfaces/MoveFolder.ts delete mode 100644 libs/interfaces/PurgeCache.ts delete mode 100644 libs/interfaces/Rename.ts delete mode 100644 libs/interfaces/Transformation.ts delete mode 100644 libs/interfaces/UploadOptions.ts delete mode 100644 libs/interfaces/UploadResponse.ts delete mode 100644 libs/interfaces/UrlOptions.ts delete mode 100644 libs/interfaces/index.ts delete mode 100644 libs/interfaces/webhookEvent.ts delete mode 100644 libs/manage/cache.ts delete mode 100644 libs/manage/custom-metadata-field.ts delete mode 100644 libs/manage/file.ts delete mode 100644 libs/manage/index.ts delete mode 100644 libs/signature/index.ts delete mode 100644 libs/upload/index.ts delete mode 100644 libs/url/builder.ts delete mode 100644 libs/url/index.ts create mode 100644 packages/mcp-server/README.md create mode 100644 packages/mcp-server/build create mode 100644 packages/mcp-server/jest.config.ts create mode 100644 packages/mcp-server/package.json create mode 100644 packages/mcp-server/scripts/postprocess-dist-package-json.cjs create mode 100644 packages/mcp-server/src/code-tool-paths.cts create mode 100644 packages/mcp-server/src/code-tool-types.ts create mode 100644 packages/mcp-server/src/code-tool-worker.ts create mode 100644 packages/mcp-server/src/code-tool.ts create mode 100644 packages/mcp-server/src/compat.ts create mode 100644 packages/mcp-server/src/dynamic-tools.ts create mode 100644 packages/mcp-server/src/filtering.ts create mode 100644 packages/mcp-server/src/headers.ts create mode 100644 packages/mcp-server/src/http.ts create mode 100644 packages/mcp-server/src/index.ts create mode 100644 packages/mcp-server/src/options.ts create mode 100644 packages/mcp-server/src/server.ts create mode 100644 packages/mcp-server/src/stdio.ts create mode 100644 packages/mcp-server/src/tools.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts create mode 100644 packages/mcp-server/src/tools/assets/list-assets.ts create mode 100644 packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts create mode 100644 packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts create mode 100644 packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/copy-files.ts create mode 100644 packages/mcp-server/src/tools/files/delete-files.ts create mode 100644 packages/mcp-server/src/tools/files/get-files.ts create mode 100644 packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts create mode 100644 packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts create mode 100644 packages/mcp-server/src/tools/files/move-files.ts create mode 100644 packages/mcp-server/src/tools/files/rename-files.ts create mode 100644 packages/mcp-server/src/tools/files/update-files.ts create mode 100644 packages/mcp-server/src/tools/files/upload-files.ts create mode 100644 packages/mcp-server/src/tools/files/versions/delete-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/get-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/list-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/restore-files-versions.ts create mode 100644 packages/mcp-server/src/tools/folders/copy-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/create-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/delete-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/job/get-folders-job.ts create mode 100644 packages/mcp-server/src/tools/folders/move-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/rename-folders.ts create mode 100644 packages/mcp-server/src/tools/index.ts create mode 100644 packages/mcp-server/src/tools/types.ts create mode 100644 packages/mcp-server/tests/compat.test.ts create mode 100644 packages/mcp-server/tests/dynamic-tools.test.ts create mode 100644 packages/mcp-server/tests/options.test.ts create mode 100644 packages/mcp-server/tests/tools.test.ts create mode 100644 packages/mcp-server/tsc-multi.json create mode 100644 packages/mcp-server/tsconfig.build.json create mode 100644 packages/mcp-server/tsconfig.dist-src.json create mode 100644 packages/mcp-server/tsconfig.json create mode 100644 packages/mcp-server/yarn.lock delete mode 100644 sample/README.md delete mode 100644 sample/index.js delete mode 100644 sample/package.json delete mode 100644 sample/test_image.jpg create mode 100755 scripts/bootstrap create mode 100755 scripts/build create mode 100755 scripts/build-all create mode 100755 scripts/format create mode 100755 scripts/lint create mode 100755 scripts/mock create mode 100644 scripts/publish-packages.ts create mode 100755 scripts/test create mode 100644 scripts/utils/attw-report.cjs create mode 100755 scripts/utils/check-is-in-git-install.sh create mode 100644 scripts/utils/check-version.cjs create mode 100644 scripts/utils/fix-index-exports.cjs create mode 100755 scripts/utils/git-swap.sh create mode 100644 scripts/utils/make-dist-package-json.cjs create mode 100644 scripts/utils/postprocess-files.cjs create mode 100755 scripts/utils/upload-artifact.sh create mode 100644 src/api-promise.ts create mode 100644 src/client.ts create mode 100644 src/core/README.md create mode 100644 src/core/api-promise.ts create mode 100644 src/core/error.ts create mode 100644 src/core/resource.ts create mode 100644 src/core/uploads.ts create mode 100644 src/error.ts create mode 100644 src/index.ts create mode 100644 src/internal/README.md create mode 100644 src/internal/builtin-types.ts create mode 100644 src/internal/detect-platform.ts create mode 100644 src/internal/errors.ts create mode 100644 src/internal/headers.ts create mode 100644 src/internal/parse.ts create mode 100644 src/internal/request-options.ts create mode 100644 src/internal/shim-types.ts create mode 100644 src/internal/shims.ts create mode 100644 src/internal/to-file.ts create mode 100644 src/internal/types.ts create mode 100644 src/internal/uploads.ts create mode 100644 src/internal/utils.ts create mode 100644 src/internal/utils/base64.ts create mode 100644 src/internal/utils/bytes.ts create mode 100644 src/internal/utils/env.ts create mode 100644 src/internal/utils/log.ts create mode 100644 src/internal/utils/path.ts create mode 100644 src/internal/utils/sleep.ts create mode 100644 src/internal/utils/uuid.ts create mode 100644 src/internal/utils/values.ts create mode 100644 src/lib/.keep create mode 100644 src/resource.ts create mode 100644 src/resources.ts create mode 100644 src/resources/accounts.ts create mode 100644 src/resources/accounts/accounts.ts create mode 100644 src/resources/accounts/index.ts create mode 100644 src/resources/accounts/origins.ts create mode 100644 src/resources/accounts/url-endpoints.ts create mode 100644 src/resources/accounts/usage.ts create mode 100644 src/resources/assets.ts create mode 100644 src/resources/beta.ts create mode 100644 src/resources/beta/beta.ts create mode 100644 src/resources/beta/index.ts create mode 100644 src/resources/beta/v2.ts create mode 100644 src/resources/beta/v2/files.ts create mode 100644 src/resources/beta/v2/index.ts create mode 100644 src/resources/beta/v2/v2.ts create mode 100644 src/resources/cache.ts create mode 100644 src/resources/cache/cache.ts create mode 100644 src/resources/cache/index.ts create mode 100644 src/resources/cache/invalidation.ts create mode 100644 src/resources/custom-metadata-fields.ts create mode 100644 src/resources/files.ts create mode 100644 src/resources/files/bulk.ts create mode 100644 src/resources/files/files.ts create mode 100644 src/resources/files/index.ts create mode 100644 src/resources/files/metadata.ts create mode 100644 src/resources/files/versions.ts create mode 100644 src/resources/folders.ts create mode 100644 src/resources/folders/folders.ts create mode 100644 src/resources/folders/index.ts create mode 100644 src/resources/folders/job.ts create mode 100644 src/resources/index.ts create mode 100644 src/resources/shared.ts create mode 100644 src/resources/webhooks.ts create mode 100644 src/uploads.ts create mode 100644 src/version.ts delete mode 100644 test-e2e.sh create mode 100644 tests/api-resources/accounts/origins.test.ts create mode 100644 tests/api-resources/accounts/url-endpoints.test.ts create mode 100644 tests/api-resources/accounts/usage.test.ts create mode 100644 tests/api-resources/assets.test.ts create mode 100644 tests/api-resources/beta/v2/files.test.ts create mode 100644 tests/api-resources/cache/invalidation.test.ts create mode 100644 tests/api-resources/custom-metadata-fields.test.ts create mode 100644 tests/api-resources/files/bulk.test.ts create mode 100644 tests/api-resources/files/files.test.ts create mode 100644 tests/api-resources/files/metadata.test.ts create mode 100644 tests/api-resources/files/versions.test.ts create mode 100644 tests/api-resources/folders/folders.test.ts create mode 100644 tests/api-resources/folders/job.test.ts create mode 100644 tests/api-resources/webhooks.test.ts create mode 100644 tests/base64.test.ts create mode 100644 tests/buildHeaders.test.ts delete mode 100644 tests/cache.js delete mode 100644 tests/custom-metadata-field.js delete mode 100644 tests/data/index.js delete mode 100644 tests/data/test_image.jpg delete mode 100644 tests/e2e/node-js/index.js delete mode 100644 tests/e2e/typescript/index.ts delete mode 100644 tests/e2e/typescript/tsconfig.json create mode 100644 tests/form.test.ts delete mode 100644 tests/helpers/errors.js delete mode 100644 tests/helpers/spies.js create mode 100644 tests/index.test.ts delete mode 100644 tests/initialization.js delete mode 100644 tests/mediaLibrary.js create mode 100644 tests/path.test.ts delete mode 100644 tests/phash.js delete mode 100644 tests/response-metadata.js create mode 100644 tests/stringifyQuery.test.ts delete mode 100644 tests/unit.js delete mode 100644 tests/upload.js create mode 100644 tests/uploads.test.ts delete mode 100644 tests/url-generation.js delete mode 100644 tests/webhook-signature.js create mode 100644 tsc-multi.json create mode 100644 tsconfig.build.json create mode 100644 tsconfig.deno.json create mode 100644 tsconfig.dist-src.json delete mode 100644 utils/authorization.ts delete mode 100644 utils/hamming-distance.d.ts delete mode 100644 utils/phash.ts delete mode 100644 utils/request.ts delete mode 100644 utils/respond.ts delete mode 100644 utils/transformation.ts delete mode 100644 utils/urlFormatter.ts delete mode 100644 utils/webhook-signature.ts diff --git a/.babelrc b/.babelrc deleted file mode 100644 index ed723b35..00000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - "@babel/preset-typescript" - ], - "plugins" : [ - "babel-plugin-replace-ts-export-assignment" - ] -} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..43fd5a73 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Development", + "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", + "features": { + "ghcr.io/devcontainers/features/node:1": {} + }, + "postCreateCommand": "yarn install", + "customizations": { + "vscode": { + "extensions": ["esbenp.prettier-vscode"] + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..870fe3ab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: CI +on: + push: + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' + +jobs: + lint: + timeout-minutes: 10 + name: lint + runs-on: ${{ github.repository == 'stainless-sdks/imagekit-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Check types + run: ./scripts/lint + + build: + timeout-minutes: 5 + name: build + runs-on: ${{ github.repository == 'stainless-sdks/imagekit-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Check build + run: ./scripts/build + + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/imagekit-typescript' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + if: github.repository == 'stainless-sdks/imagekit-typescript' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + + - name: Upload MCP Server tarball + if: github.repository == 'stainless-sdks/imagekit-typescript' + env: + URL: https://pkg.stainless.com/s?subpackage=mcp-server + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + BUILD_PATH: packages/mcp-server/dist + run: ./scripts/utils/upload-artifact.sh + test: + timeout-minutes: 10 + name: test + runs-on: ${{ github.repository == 'stainless-sdks/imagekit-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Build + run: ./scripts/build + + - name: Run tests + run: ./scripts/test diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml deleted file mode 100644 index 7263c90c..00000000 --- a/.github/workflows/nodejs.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Node CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [12.x, 14.x, 16.x, 18.x] - - steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: Test and report coverage - run: | - npm i -g yarn - yarn install - yarn test - yarn test-e2e - # yarn report-coverage - env: - CI: true diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml deleted file mode 100644 index e1b73f85..00000000 --- a/.github/workflows/npmpublish.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish - -on: - release: - types: [published] - - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [12.x, 14.x] - - steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: npm install, build, and test - run: | - npm i -g yarn - yarn install - yarn test - yarn test-e2e - env: - CI: true - - publish: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: 12 - registry-url: https://registry.npmjs.org/ - - name: yarn publish - run: | - npm i -g yarn - yarn config set //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN - yarn install - yarn publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} - CI: true diff --git a/.gitignore b/.gitignore index 919ac523..d98d51a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,10 @@ +.prism.log node_modules -.vscode -.nyc_output -coverage.lcov -coverage -.DS_Store -dist* -tests/e2e/node-js/package.json -tests/e2e/node-js/yarn.lock -tests/e2e/typescript/package.json -tests/e2e/typescript/yarn.lock -tests/e2e/typescript/index.js -.cache yarn-error.log -*.tgz \ No newline at end of file +codegen.log +Brewfile.lock.json +dist +dist-deno +/*.tgz +.idea/ + diff --git a/.mocharc.json b/.mocharc.json deleted file mode 100644 index fe5128ba..00000000 --- a/.mocharc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extension": ["ts"], - "spec": "tests/*.js", - "require": "babel-register.js" -} \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index eaa22bd7..00000000 --- a/.npmignore +++ /dev/null @@ -1,19 +0,0 @@ -.github -tests -sample -.npmignore -.babelrc -coverage -babel-register.js -.nyc_output -.vscode -.DS_Store -.mocharc.json -.nycrc -libs/* -utils/* -tsconfig* -fixup.sh -index.ts -*.tgz -test-e2e.sh \ No newline at end of file diff --git a/.nycrc b/.nycrc deleted file mode 100644 index be4975e7..00000000 --- a/.nycrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "check-coverage": true, - "lines": 95 -} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7cc13dd1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +CHANGELOG.md +/ecosystem-tests/*/** +/node_modules +/deno + +# don't format tsc output, will break source maps +dist diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..af75adaf --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "arrowParens": "always", + "experimentalTernaries": true, + "printWidth": 110, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.stats.yml b/.stats.yml new file mode 100644 index 00000000..67507d30 --- /dev/null +++ b/.stats.yml @@ -0,0 +1,4 @@ +configured_endpoints: 42 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml +openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 +config_hash: 1dd1a96eff228aa2567b9973c36f5593 diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..e4feee60 --- /dev/null +++ b/Brewfile @@ -0,0 +1 @@ +brew "node" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7ec52687..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,38 +0,0 @@ -## Changelog - -### SDK Version 6.0.0 - -### Breaking changes - -**1. `listFiles` API response type** -* The `listFiles` method now returns a unified response type, ListFileResponse, which is an array of both `FileObject` and `FolderObject`. Previously, the response contained only `FileObject`. The `type` property in the response object indicates whether the object is a file or a folder. Even though this change has been made to just the type of the return object, it can be considered a breaking change so it may require require any code relying on the `listFiles` response to be updated. - -``` -const result = await imagekit.listFiles({ skip: 0, limit: 10 }); - -# Before (Pre-version 5.3.0) -result.forEach((item) => { - console.log(item); -}); - -# After (Version 5.3.0 and above) -result.forEach((item) => { - if (item.type === "folder") { - console.log(item) // item is of type FolderObject - } else { - console.log(item) // item is of type FileObject - } -}); -``` - - -### SDK Version 5.0.0 - -#### Breaking changes - -**1. Overlay syntax update** -* In version 5.0.0, we've removed the old overlay syntax parameters for transformations, such as `oi`, `ot`, `obg`, and [more](https://docs.imagekit.io/features/image-transformations/overlay). These parameters are deprecated and will start returning errors when used in URLs. Please migrate to the new layers syntax that supports overlay nesting, provides better positional control, and allows more transformations at the layer level. You can start with [examples](https://docs.imagekit.io/features/image-transformations/overlay-using-layers#examples) to learn quickly. -* You can migrate to the new layers syntax using the `raw` transformation parameter. - -**2. Remove Node.js 10.x support** -* In version 5.0.0, we've removed support for Node.js version 10.x. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ceb97606 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +## Setting up the environment + +This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). +Other package managers may work but are not officially supported for development. + +To set up the repository, run: + +```sh +$ yarn +$ yarn build +``` + +This will install all the required dependencies and build output files to `dist/`. + +## Modifying/Adding code + +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may +result in merge conflicts between manual patches and changes from the generator. The generator will never +modify the contents of the `src/lib/` and `examples/` directories. + +## Adding and running examples + +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. + +```ts +// add an example to examples/.ts + +#!/usr/bin/env -S npm run tsn -T +… +``` + +```sh +$ chmod +x examples/.ts +# run the example against your api +$ yarn tsn -T examples/.ts +``` + +## Using the repository from source + +If you’d like to use the repository from source, you can either install from git or link to a cloned repository: + +To install via git: + +```sh +$ npm install git+ssh://git@github.com:stainless-sdks/imagekit-typescript.git +``` + +Alternatively, to link a local copy of the repo: + +```sh +# Clone +$ git clone https://www.github.com/stainless-sdks/imagekit-typescript +$ cd imagekit-typescript + +# With yarn +$ yarn link +$ cd ../my-package +$ yarn link @imagekit/nodejs + +# With pnpm +$ pnpm link --global +$ cd ../my-package +$ pnpm link -—global @imagekit/nodejs +``` + +## Running tests + +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. + +```sh +$ npx prism mock path/to/your/openapi.yml +``` + +```sh +$ yarn run test +``` + +## Linting and formatting + +This repository uses [prettier](https://www.npmjs.com/package/prettier) and +[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. + +To lint: + +```sh +$ yarn lint +``` + +To format and fix all lint issues automatically: + +```sh +$ yarn fix +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e7a4d160 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Image Kit + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 37a4832f..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -Released under the MIT license. \ No newline at end of file diff --git a/README.md b/README.md index dbbf7a59..3282e615 100644 --- a/README.md +++ b/README.md @@ -1,1293 +1,405 @@ -[ImageKit.io](https://imagekit.io) +# Image Kit TypeScript API Library -# ImageKit.io Node.js SDK +[![NPM version]()](https://npmjs.org/package/@imagekit/nodejs) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@imagekit/nodejs) -[![Node CI](https://github.com/imagekit-developer/imagekit-nodejs/workflows/Node%20CI/badge.svg)](https://github.com/imagekit-developer/imagekit-nodejs/) -[![npm version](https://img.shields.io/npm/v/imagekit)](https://www.npmjs.com/package/imagekit) -[![codecov](https://codecov.io/gh/imagekit-developer/imagekit-nodejs/branch/master/graph/badge.svg)](https://codecov.io/gh/imagekit-developer/imagekit-nodejs) -[![Try imagekit on RunKit](https://badge.runkitcdn.com/imagekit.svg)](https://npm.runkit.com/imagekit) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Twitter Follow](https://img.shields.io/twitter/follow/imagekitio?label=Follow&style=social)](https://twitter.com/ImagekitIo) +This library provides convenient access to the Image Kit REST API from server-side TypeScript or JavaScript. -Node.js SDK for [ImageKit](https://imagekit.io/) implements the new APIs and interface for different file operations. +The REST API documentation can be found on [imagekit.io](https://imagekit.io). The full API of this library can be found in [api.md](api.md). -ImageKit is complete media storage, optimization, and transformation solution that comes with an [image and video CDN](https://imagekit.io/features/imagekit-infrastructure). It can be integrated with your existing infrastructure - storage like AWS S3, web servers, your CDN, and custom domain names, allowing you to deliver optimized images in minutes with minimal code changes. - -##### Table of contents -* [Changelog](#changelog) -* [Installation](#installation) -* [Initialization](#initialization) -* [URL generation](#url-generation) -* [File upload](#file-upload) -* [File management](#file-management) -* [Utility functions](#utility-functions) -* [Rate limits](#rate-limits) -* [Support](#support) -* [Links](#links) +It is generated with [Stainless](https://www.stainless.com/). ## Installation -Use the following command to download this module. Use the optional `--save` parameter if you wish to save the dependency in your `package.json` file. - -``` -npm install imagekit --save -# or -pnpm install imagekit --save -# or -bun install imagekit // if you are using [Bun](https://bun.sh/) compiler -# or -yarn add imagekit +```sh +npm install git+ssh://git@github.com:stainless-sdks/imagekit-typescript.git ``` -## Initialization - -```js -import ImageKit from "imagekit"; - -// or - -var ImageKit = require("imagekit"); - -var imagekit = new ImageKit({ - publicKey : "your_public_api_key", - privateKey : "your_private_api_key", - urlEndpoint : "https://ik.imagekit.io/your_imagekit_id/" -}); -``` +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install @imagekit/nodejs` ## Usage -You can use this Node.js SDK for three different methods - URL generation, file upload, and media management operations. The usage of the SDK has been explained below. -* `URL Generation` -* `File Upload` -* `File Management` - -## URL Generation - -**1. Using image path and image hostname or endpoint** - -This method allows you to create an URL to access a file using the relative file path and the ImageKit URL endpoint (`urlEndpoint`). The file can be an image, video or any other static file supported by ImageKit. +The full API of this library can be found in [api.md](api.md). + ```js -// For URL Generation, works for both images and videos -var imageURL = imagekit.url({ - path : "/default-image.jpg", - urlEndpoint : "https://ik.imagekit.io/your_imagekit_id/endpoint/", - transformation : [{ - "height" : "300", - "width" : "400" - }] -}); -``` - -This results in a URL like - -``` -https://ik.imagekit.io/your_imagekit_id/endpoint/tr:h-300,w-400/default-image.jpg -``` +import ImageKit from '@imagekit/nodejs'; -**2. Using full image URL** - -This method allows you to add transformation parameters to an absolute URL. For example, if you have configured a custom CNAME and have absolute asset URLs in your database or CMS, you will often need this. - - -```js -var imageURL = imagekit.url({ - src : "https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg", - transformation : [{ - "height" : "300", - "width" : "400" - }] +const client = new ImageKit({ + privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + password: process.env['ORG_MY_PASSWORD_TOKEN'], // This is the default and can be omitted }); -``` - -This results in a URL like - -``` -https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg?tr=h-300%2Cw-400 -``` - -The `.url()` method accepts the following parameters - -| Option | Description | -| :----------------| :----------------------------- | -| urlEndpoint | Optional. The base URL to be appended before the path of the image. If not specified, the URL Endpoint specified at the time of SDK initialization is used. For example, https://ik.imagekit.io/your_imagekit_id/endpoint/ | -| path | Conditional. This is the path at which the image exists. For example, `/path/to/image.jpg`. Either the `path` or `src` parameter needs to be specified for URL generation. | -| src | Conditional. This is the complete URL of an image already mapped to ImageKit. For example, `https://ik.imagekit.io/your_imagekit_id/endpoint/path/to/image.jpg`. Either the `path` or `src` parameter needs to be specified for URL generation. | -| transformation | Optional. An array of objects specifying the transformation to be applied in the URL. The transformation name and the value should be specified as a key-value pair in the object. Different steps of a [chained transformation](https://docs.imagekit.io/features/image-transformations/chained-transformations) can be specified as different objects of the array. The complete list of supported transformations in the SDK and some examples of using them are given later. If you use a transformation name that is not specified in the SDK, it gets applied as it is in the URL. | -| transformationPosition | Optional. The default value is `path` that places the transformation string as a path parameter in the URL. It can also be specified as `query`, which adds the transformation string as the URL's query parameter `tr`. If you use the `src` parameter to create the URL, then the transformation string is always added as a query parameter. | -| queryParameters | Optional. These are the other query parameters that you want to add to the final URL. These can be any query parameters and not necessarily related to ImageKit. Especially useful if you want to add some versioning parameter to your URLs. | -| signed | Optional. Boolean. Default is `false`. If set to `true`, the SDK generates a signed image URL adding the image signature to the image URL. If you create a URL using the `src` parameter instead of `path`, then do correct `urlEndpoint` for this to work. Otherwise returned URL will have the wrong signature | -| expireSeconds | Optional. Integer. Meant to be used along with the `signed` parameter to specify the time in seconds from now when the URL should expire. If specified, the URL contains the expiry timestamp in the URL, and the image signature is modified accordingly. | - -#### Examples of generating URLs - -**1. Chained Transformations as a query parameter** -```js -var imageURL = imagekit.url({ - path : "/default-image.jpg", - urlEndpoint : "https://ik.imagekit.io/your_imagekit_id/endpoint/", - transformation : [{ - "height" : "300", - "width" : "400" - }, { - "rotation" : 90 - }], - transformationPosition : "query" +const response = await client.files.upload({ + file: fs.createReadStream('path/to/file'), + fileName: 'file-name.jpg', }); -``` -``` -https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg?tr=h-300%2Cw-400%3Art-90 -``` - -**2. Sharpening and contrast transforms and a progressive JPG image** -There are some transforms like [Sharpening](https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation) that can be added to the URL with or without any other value. To use such transforms without specifying a value, specify the value as "-" in the transformation object. Otherwise, specify the value that you want to be added to this transformation. - -```js -var imageURL = imagekit.url({ - src : "https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg", - transformation : [{ - "format" : "jpg", - "progressive" : "true", - "effectSharpen" : "-", - "effectContrast" : "1" - }] -}); -``` -``` -//Note that because the `src` parameter was used, the transformation string gets added as a query parameter `tr` -https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg?tr=f-jpg%2Cpr-true%2Ce-sharpen%2Ce-contrast-1 -``` - -**3. Signed URL that expires in 300 seconds with the default URL endpoint and other query parameters** -```js -var imageURL = imagekit.url({ - path : "/default-image.jpg", - queryParameters : { - "v" : "123" - }, - transformation : [{ - "height" : "300", - "width" : "400" - }], - signed : true, - expireSeconds : 300 -}); +console.log(response.videoCodec); ``` -``` -https://ik.imagekit.io/your_imagekit_id/tr:h-300,w-400/default-image.jpg?v=123&ik-t=1567358667&ik-s=f2c7cdacbe7707b71a83d49cf1c6110e3d701054 -``` - -**4. Adding overlays** -ImageKit.io enables you to apply overlays to [images](https://docs.imagekit.io/features/image-transformations/overlay-using-layers) and [videos](https://docs.imagekit.io/features/video-transformation/overlay) using the raw parameter with the concept of [layers](https://docs.imagekit.io/features/image-transformations/overlay-using-layers#layers). The raw parameter facilitates incorporating transformations directly in the URL. A layer is a distinct type of transformation that allows you to define an asset to serve as an overlay, along with its positioning and additional transformations. +### Request & Response types -**Text as overlays** +This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: -You can add any text string over a base video or image using a text layer (l-text). + +```ts +import ImageKit from '@imagekit/nodejs'; -For example: - -```js -var imageURL = imagekit.url({ - src: "https://ik.imagekit.io/your_imagekit_id/default-image.jpg", - transformation: [{ - "width": 400, - "height": 300 - "raw": "l-text,i-Imagekit,fs-50,l-end" - }] +const client = new ImageKit({ + privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + password: process.env['ORG_MY_PASSWORD_TOKEN'], // This is the default and can be omitted }); -``` -**Sample Result URL** -``` -https://ik.imagekit.io/your_imagekit_id/tr:h-300,w-400,l-text,i-Imagekit,fs-50,l-end/default-image.jpg -``` - -**Image as overlays** -You can add an image over a base video or image using an image layer (l-image). - -For example: - -```js -var imageURL = imagekit.url({ - src: "https://ik.imagekit.io/your_imagekit_id/default-image.jpg", - transformation: [{ - "width": 400, - "height": 300 - "raw": "l-image,i-default-image.jpg,w-100,b-10_CDDC39,l-end" - }] -}); -``` -**Sample Result URL** +const params: ImageKit.FileUploadParams = { + file: fs.createReadStream('path/to/file'), + fileName: 'file-name.jpg', +}; +const response: ImageKit.FileUploadResponse = await client.files.upload(params); ``` -https://ik.imagekit.io/your_imagekit_id/tr:h-300,w-400,l-image,i-default-image.jpg,w-100,b-10_CDDC39,l-end/default-image.jpg -``` - -**Solid color blocks as overlays** -You can add solid color blocks over a base video or image using an image layer (l-image). +Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. -For example: +## File uploads -```js -var imageURL = imagekit.url({ - src: "https://ik.imagekit.io/your_imagekit_id/img/sample-video.mp4", - transformation: [{ - "width": 400, - "height": 300 - "raw": "l-image,i-ik_canvas,bg-FF0000,w-300,h-100,l-end" - }] -}); -``` -**Sample Result URL** -``` -https://ik.imagekit.io/your_imagekit_id/tr:h-300,w-400,l-image,i-ik_canvas,bg-FF0000,w-300,h-100,l-end/img/sample-video.mp4 -``` +Request parameters that correspond to file uploads can be passed in many different forms: -**5. Arithmetic expressions in transformations** +- `File` (or an object with the same structure) +- a `fetch` `Response` (or an object with the same structure) +- an `fs.ReadStream` +- the return value of our `toFile` helper -ImageKit allows use of [arithmetic expressions](https://docs.imagekit.io/features/arithmetic-expressions-in-transformations) in certain dimension and position-related parameters, making media transformations more flexible and dynamic. +```ts +import fs from 'fs'; +import ImageKit, { toFile } from '@imagekit/nodejs'; -For example: +const client = new ImageKit(); -```js -var imageURL = imagekit.url({ - src: "https://ik.imagekit.io/your_imagekit_id/default-image.jpg", - transformation: [{ - "width": "iw_div_4", - "height": "ih_div_2", - "border": "cw_mul_0.05_yellow" - }] -}); -``` +// If you have access to Node `fs` we recommend using `fs.createReadStream()`: +await client.files.upload({ file: fs.createReadStream('/path/to/file'), fileName: 'fileName' }); -**Sample Result URL** -``` -https://ik.imagekit.io/your_imagekit_id/tr:w-iw_div_4,h-ih_div_2,b-cw_mul_0.05_yellow/default-image.jpg -``` +// Or if you have the web `File` API you can pass a `File` instance: +await client.files.upload({ file: new File(['my bytes'], 'file'), fileName: 'fileName' }); +// You can also pass a `fetch` `Response`: +await client.files.upload({ file: await fetch('https://somesite/file'), fileName: 'fileName' }); -#### List of supported transformations - -See the complete list of transformations supported in ImageKit [here](https://docs.imagekit.io/features/image-transformations). The SDK gives a name to each transformation parameter e.g. `height` for `h` and `width` for `w` parameter. It makes your code more readable. If the property does not match any of the following supported options, it is added as it is. - -If you want to generate transformations in your application and add them to the URL as it is, use the `raw` parameter. - - -| Supported Transformation Name | Translates to parameter | -|-------------------------------|-------------------------| -| height | h | -| width | w | -| aspectRatio | ar | -| quality | q | -| crop | c | -| cropMode | cm | -| x | x | -| y | y | -| focus | fo | -| format | f | -| radius | r | -| background | bg | -| border | b | -| rotation | rt | -| blur | bl | -| named | n | -| progressive | pr | -| lossless | lo | -| trim | t | -| metadata | md | -| colorProfile | cp | -| defaultImage | di | -| dpr | dpr | -| effectSharpen | e-sharpen | -| effectUSM | e-usm | -| effectContrast | e-contrast | -| effectGray | e-grayscale | -| effectShadow | e-shadow | -| effectGradient | e-gradient | -| original | orig | -| raw | `replaced by the parameter value` | - - - -## File Upload - -The SDK provides a simple interface using the `.upload()` method to upload files to the ImageKit Media Library. It accepts all the parameters supported by the [ImageKit Upload API](https://docs.imagekit.io/api-reference/upload-file-api/server-side-file-upload). - -The `upload()` method requires at least the `file` and the `fileName` parameter to upload a file and returns a callback with the `error` and `result` as arguments. You can pass other parameters supported by the ImageKit upload API using the same parameter name as specified in the upload API documentation. For example, to set tags for a file at the upload time, use the `tags` parameter as defined in the [documentation here](https://docs.imagekit.io/api-reference/upload-file-api/server-side-file-upload). - -Sample usage -```js -// Using Callback Function - -imagekit.upload({ - file : , //required - fileName : "my_file_name.jpg", //required - extensions: [ - { - name: "google-auto-tagging", - maxTags: 5, - minConfidence: 95 - } - ], - transformation: { - pre: 'l-text,i-Imagekit,fs-50,l-end', - post: [ - { - type: 'transformation', - value: 'w-100' - } - ] - }, - checks: {`"file.size" < "1mb"`}, // To run server side checks before uploading files. Notice the quotes around file.size and 1mb. - isPublished: true -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises - -imagekit.upload({ - file : , //required - fileName : "my_file_name.jpg", //required - extensions: [ - { - name: "google-auto-tagging", - maxTags: 5, - minConfidence: 95 - } - ], - transformation: { - pre: 'l-text,i-Imagekit,fs-50,l-end', - post: [ - { - type: 'transformation', - value: 'w-100' - } - ] - }, - checks={`"file.size" < "1mb"`} -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -If the upload succeeds, `error` will be `null,` and the `result` will be the same as what is received from ImageKit's servers. -If the upload fails, the `error` will be the same as what is received from ImageKit's servers, and the `result` will be null. - - - -## File Management - -The SDK provides a simple interface for all the [media APIs mentioned here](https://docs.imagekit.io/api-reference/media-api) to manage your files. You can use a callback function with all API interfaces. The first argument of the callback function is the error, and the second is the result of the API call. The error will be `null` if the API succeeds. - -**List & Search Files** - -Accepts an object specifying the parameters used to list and search files. All parameters specified in the [documentation here](https://docs.imagekit.io/api-reference/media-api/list-and-search-files) can be passed as-is with the correct values to get the results. - -```js -// Using Callback Function - -imagekit.listFiles({ - skip : 10, - limit : 10 -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises - -imagekit.listFiles({ - skip : 10, - limit : 10 -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Get File Details** - -Accepts the file ID and fetches the details as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/get-file-details). - -```js -// Using Callback Function - -imagekit.getFileDetails("file_id", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises - -imagekit.getFileDetails("file_id") -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Get File Versions** - -Accepts the file ID and fetches all the versions of that file as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/get-file-versions). - -```js -// Using Callback Function - -imagekit.getFileVersions("file_id", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises - -imagekit.getFileVersions("file_id") -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Get File version details** - -Accepts the file ID & version ID and returns the details of that specific version as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/get-file-version-details). - -```js -// Using Callback Function - -imagekit.getFileVersionDetails({ - fileId: "file_id", - versionId: "version_id" -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises - -imagekit.getFileVersionDetails({ - fileId: "file_id", - versionId: "version_id" -}) -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); +// Finally, if none of the above are convenient, you can use our `toFile` helper: +await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), fileName: 'fileName' }); +await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); ``` -**Update File Details** - -Update parameters associated with the file as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/update-file-details). The first argument to the `updateFileDetails` method is the file ID, and the second argument is an object with the parameters to be updated. - -Note: If `publish` is included in the update options, no other parameters are allowed. If any are present, an error will be returned: `Your request cannot contain any other parameters when publish is present`. - -```js -// Using Callback Function - -imagekit.updateFileDetails("file_id", { - tags : ['image_tag'], - customCoordinates : "10,10,100,100", - extensions: [ - { - name: "google-auto-tagging", - maxTags: 5, - minConfidence: 95 - } - ] -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - +## Handling errors -// Using Promises +When the library is unable to connect to the API, +or if the API returns a non-success status code (i.e., 4xx or 5xx response), +a subclass of `APIError` will be thrown: -imagekit.updateFileDetails("file_id", { - publish: { - isPublished: true, - includeFileVersions: true + +```ts +const response = await client.files + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .catch(async (err) => { + if (err instanceof ImageKit.APIError) { + console.log(err.status); // 400 + console.log(err.name); // BadRequestError + console.log(err.headers); // {server: 'nginx', ...} + } else { + throw err; } -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Bulk Add tags** - -Add tags to multiple files in a single request as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/add-tags-bulk). The method accepts an array of fileIDs of the files and an array of tags that have to be added to those files. - -```js -// Using Callback Function - -imagekit.bulkAddTags(["file_id_1", "file_id_2"], ["tag1", "tag2"], function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises - -imagekit.bulkAddTags(["file_id_1", "file_id_2"], ["tag1", "tag2"]).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Bulk Remove tags** - -Remove tags from multiple files in a single request as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/remove-tags-bulk). The method accepts an array of fileIDs of the files and an array of tags that have to be removed from those files. - -```js -// Using Callback Function - -imagekit.bulkRemoveTags(["file_id_1", "file_id_2"], ["tags"], function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises - -imagekit.bulkRemoveTags(["file_id_1", "file_id_2"], ["tags"]).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); + }); ``` -**Bulk Remove AI Tags** +Error codes are as follows: -Remove AI tags from multiple files in a single request as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/remove-aitags-bulk). The method accepts an array of fileIDs of the files and an array of tags that have to be removed from those files. +| Status Code | Error Type | +| ----------- | -------------------------- | +| 400 | `BadRequestError` | +| 401 | `AuthenticationError` | +| 403 | `PermissionDeniedError` | +| 404 | `NotFoundError` | +| 422 | `UnprocessableEntityError` | +| 429 | `RateLimitError` | +| >=500 | `InternalServerError` | +| N/A | `APIConnectionError` | -```js -// Using Callback Function - -imagekit.bulkRemoveAITags(["file_id_1", "file_id_2"], ["ai-tag1", "ai-tag2"], function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +### Retries -// Using Promises +Certain errors will be automatically retried 2 times by default, with a short exponential backoff. +Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, +429 Rate Limit, and >=500 Internal errors will all be retried by default. -imagekit.bulkRemoveAITags(["file_id_1", "file_id_2"], ["ai-tag1", "ai-tag2"]).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Delete File** - -Delete a file as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/delete-file). The method accepts the file ID of the file that has to be deleted. +You can use the `maxRetries` option to configure or disable this: + ```js -// Using Callback Function - -imagekit.deleteFile("file_id", function(error, result) { - if(error) console.log(error); - else console.log(result); +// Configure the default for all requests: +const client = new ImageKit({ + maxRetries: 0, // default is 2 }); - -// Using Promises - -imagekit.deleteFile("file_id").then(response => { - console.log(response); -}).catch(error => { - console.log(error); +// Or, configure per-request: +await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { + maxRetries: 5, }); ``` -**Delete File Version** +### Timeouts -Delete any non-current version of a file as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/delete-file-version). +Requests time out after 1 minute by default. You can configure this with a `timeout` option: -```js -// Using Callback Function - -imagekit.deleteFileVersion({ - fileId: "file_id", - versionId: "version_id", -}, function(error, result) { - if(error) console.log(error); - else console.log(result); + +```ts +// Configure the default for all requests: +const client = new ImageKit({ + timeout: 20 * 1000, // 20 seconds (default is 1 minute) }); - -// Using Promises - -imagekit.deleteFile({ - fileId: "file_id", - versionId: "version_id", -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +// Override per-request: +await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { + timeout: 5 * 1000, }); ``` -**Bulk Delete Files** +On timeout, an `APIConnectionTimeoutError` is thrown. -Delete multiple files as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/delete-files-bulk). The method accepts an array of file IDs of the files that have to be deleted. +Note that requests which time out will be [retried twice by default](#retries). -```js -// Using Callback Function +## Advanced Usage -imagekit.bulkDeleteFiles(["file_id_1", "file_id_2"], function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +### Accessing raw Response data (e.g., headers) +The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. +This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic. -// Using Promises +You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. +Unlike `.asResponse()` this method consumes the body, returning once it is parsed. -imagekit.bulkDeleteFiles(["file_id_1", "file_id_2"]).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` + +```ts +const client = new ImageKit(); -**Copy File** +const response = await client.files + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .asResponse(); +console.log(response.headers.get('X-My-Header')); +console.log(response.statusText); // access the underlying Response object -This will copy a file from one location to another as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/copy-file). - -```js -// Using Callback Function - -imagekit.copyFile({ - sourceFilePath: "/path/to/file.jpg", - destinationPath: "/folder/to/copy/into/" - includeFileVersions: false // optional -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises - -imagekit.copyFile({ - sourceFilePath: "/path/to/file.jpg", - destinationPath: "/folder/to/copy/into/", - includeFileVersions: false // optional -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); +const { data: response, response: raw } = await client.files + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .withResponse(); +console.log(raw.headers.get('X-My-Header')); +console.log(response.videoCodec); ``` -**Move File** +### Logging -This will move a file from one location to another as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/move-file). +> [!IMPORTANT] +> All log messages are intended for debugging only. The format and content of log messages +> may change between releases. -```js -// Using Callback Function - -imagekit.moveFile({ - sourceFilePath: "/path/to/file.jpg", - destinationPath: "/folder/to/copy/into/", -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises +#### Log levels -imagekit.moveFile({ - sourceFilePath: "/path/to/file.jpg", - destinationPath: "/folder/to/copy/into/", -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` +The log level can be configured in two ways: -**Rename File** - -Rename the file as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/rename-file). - -```js -// Using Callback Function - -imagekit.renameFile({ - filePath: "/path/to/old-file-name.jpg", - newFileName: "new-file-name.jpg", - purgeCache: false // optional -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +1. Via the `IMAGE_KIT_LOG` environment variable +2. Using the `logLevel` client option (overrides the environment variable if set) -// Using Promises +```ts +import ImageKit from '@imagekit/nodejs'; -imagekit.renameFile({ - filePath: "/path/to/old-file-name.jpg", - newFileName: "new-file-name.jpg", - purgeCache: false // optional -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const client = new ImageKit({ + logLevel: 'debug', // Show all log messages }); ``` -**Restore File Version** +Available log levels, from most to least verbose: -Restore the file version as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/restore-file-version). +- `'debug'` - Show debug messages, info, warnings, and errors +- `'info'` - Show info messages, warnings, and errors +- `'warn'` - Show warnings and errors (default) +- `'error'` - Show only errors +- `'off'` - Disable all logging -```js -// Using Callback Function - -imagekit.restoreFileVersion({ - fileId: "file_id", - versionId: "version_id" -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies. +Some authentication-related headers are redacted, but sensitive data in request and response bodies +may still be visible. -// Using Promises +#### Custom logger -imagekit.restoreFileVersion({ - fileId: "file_id", - versionId: "version_id" -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` +By default, this library logs to `globalThis.console`. You can also provide a custom logger. +Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue. -**Create Folder** +When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages +below the configured level will not be sent to your logger. -This will create a new folder as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/create-folder). +```ts +import ImageKit from '@imagekit/nodejs'; +import pino from 'pino'; -```js -// Using Callback Function - -imagekit.createFolder({ - folderName: "new_folder", - parentFolderPath: "source/folder/path" -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - -// Using Promises +const logger = pino(); -imagekit.createFolder({ - folderName: "new_folder", - parentFolderPath: "source/folder/path" -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const client = new ImageKit({ + logger: logger.child({ name: 'ImageKit' }), + logLevel: 'debug', // Send all messages to pino, allowing it to filter }); ``` -**Delete Folder** +### Making custom/undocumented requests -This will delete the specified Folder and all nested files & folders as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/delete-folder). - -```js -// Using Callback Function +This library is typed for convenient access to the documented API. If you need to access undocumented +endpoints, params, or response properties, the library can still be used. -imagekit.deleteFolder("folderPath", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +#### Undocumented endpoints -// Using Promises +To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. +Options on the client, such as retries, will be respected when making these requests. -imagekit.deleteFolder("folderPath").then(response => { - console.log(response); -}).catch(error => { - console.log(error); +```ts +await client.post('/some/path', { + body: { some_prop: 'foo' }, + query: { some_query_arg: 'bar' }, }); ``` -**Copy Folder** - -This will copy one Folder into another as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/copy-folder). - -```js -// Using Callback Function - -imagekit.copyFolder({ - sourceFolderPath: "/folder/to/copy", - destinationPath: "/folder/to/copy/into/", - includeFileVersions: false // optional -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +#### Undocumented request params -// Using Promises +To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented +parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you +send will be sent as-is. -imagekit.copyFolder({ - sourceFolderPath: "/folder/to/copy", - destinationPath: "/folder/to/copy/into/", - includeFileVersions: false // optional -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +```ts +client.files.upload({ + // ... + // @ts-expect-error baz is not yet public + baz: 'undocumented option', }); ``` -**Move Folder** - -This will move one Folder into another as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/move-folder). - -```js -// Using Callback Function - -imagekit.moveFolder({ - sourceFolderPath: "/folder/to/move", - destinationPath: "/folder/to/move/into/" -}, function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +For requests with the `GET` verb, any extra params will be in the query, all other requests will send the +extra param in the body. -// Using Promises +If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request +options. -imagekit.moveFolder({ - sourceFolderPath: "/folder/to/move", - destinationPath: "/folder/to/move/into/" -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` +#### Undocumented response properties -**Get bulk job status** +To access undocumented response properties, you may access the response object with `// @ts-expect-error` on +the response object, or cast the response object to the requisite type. Like the request params, we do not +validate or strip extra properties from the response from the API. -This allows us to get a bulk operation status e.g. copy or move Folder as per [API documentation here](https://docs.imagekit.io/api-reference/media-api/copy-move-folder-status). This method accepts `jobId` that is returned by copy and move folder operations. +### Customizing the fetch client -```js -// Using Callback Function +By default, this library expects a global `fetch` function is defined. -imagekit.getBulkJobStatus("jobId", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); +If you want to use a different `fetch` function, you can either polyfill the global: -// Using Promises +```ts +import fetch from 'my-fetch'; -imagekit.getBulkJobStatus("jobId").then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); +globalThis.fetch = fetch; ``` -**Purge Cache** - -Programmatically issue a clear cache request as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/purge-cache). Accepts the full URL of the file for which the cache has to be cleared. - -```js -// Using Callback Function - -imagekit.purgeCache("full_url", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - +Or pass it to the client: -// Using Promises +```ts +import ImageKit from '@imagekit/nodejs'; +import fetch from 'my-fetch'; -imagekit.purgeCache("full_url").then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); +const client = new ImageKit({ fetch }); ``` -**Purge Cache Status** +### Fetch options -Get the purge cache request status using the request ID returned when a purge cache request gets submitted as per the [API documentation here](https://docs.imagekit.io/api-reference/media-api/purge-cache-status) +If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) -```js -// Using Callback Function +```ts +import ImageKit from '@imagekit/nodejs'; -imagekit.getPurgeCacheStatus("cache_request_id", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises - -imagekit.getPurgeCacheStatus("cache_request_id").then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const client = new ImageKit({ + fetchOptions: { + // `RequestInit` options + }, }); ``` -**Get File Metadata** +#### Configuring proxies -Accepts the file ID and fetches the metadata as per the [API documentation here](https://docs.imagekit.io/api-reference/metadata-api/get-image-metadata-for-uploaded-media-files). +To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy +options to requests: -```js -// Using Callback Function -imagekit.getFileMetadata("file_id", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises -imagekit.getFileMetadata("file_id") -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -You can also pass the remote URL of the image to get metadata. - -```js -// Using Callback Function -imagekit.getFileMetadata("https://ik.imagekit.io/your_imagekit_id/sample.jpg", function(error, result) { - if(error) console.log(error); - else console.log(result); -}); - - -// Using Promises -imagekit.getFileMetadata("https://ik.imagekit.io/your_imagekit_id/sample.jpg") -}).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Create a custom metadata field** - -Create a new custom metadata field as per the [API documentation here](https://docs.imagekit.io/api-reference/custom-metadata-fields-api/create-custom-metadata-field) - -```js -// Using Callback Function - -imagekit.createCustomMetadataField( - { - name: "price", - label: "price", - schema: { - type: "Number", - minValue: 1000, - maxValue: 3000 - } - }, - function(error, result) { - if(error) console.log(error); - else console.log(result); - } -); + **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] +```ts +import ImageKit from '@imagekit/nodejs'; +import * as undici from 'undici'; -// Using Promises - -imagekit.createCustomMetadataField( - { - name: "price", - label: "price", - schema: { - type: "Number", - minValue: 1000, - maxValue: 3000 - } - } -).then(response => { - console.log(response); -}).catch(error => { - console.log(error); -}); -``` - -**Get all custom metadata fields** - -Get the list of all custom metadata fields as per the [API documentation here](https://docs.imagekit.io/api-reference/custom-metadata-fields-api/get-custom-metadata-field) - -```js -// Using Callback Function - -imagekit.getCustomMetadataFields( - { - includeDeleted: false // optional - }, - function(error, result) { - if(error) console.log(error); - else console.log(result); - } -); - - -// Using Promises - -imagekit.getCustomMetadataFields( - { - includeDeleted: false // optional - } -).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); +const client = new ImageKit({ + fetchOptions: { + dispatcher: proxyAgent, + }, }); ``` -**Update a custom metadata field** + **Bun** [[docs](https://bun.sh/guides/http/proxy)] -Update a custom metadata field as per the [API documentation here](https://docs.imagekit.io/api-reference/custom-metadata-fields-api/update-custom-metadata-field) +```ts +import ImageKit from '@imagekit/nodejs'; -```js -// Using Callback Function - -imagekit.updateCustomMetadataField( - "field_id", - { - schema: { - minValue: 500, - maxValue: 2500 - } - }, - function(error, result) { - if(error) console.log(error); - else console.log(result); - } -); - - -// Using Promises - -imagekit.updateCustomMetadataField( - "field_id", - { - schema: { - minValue: 500, - maxValue: 2500 - } - }, -).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const client = new ImageKit({ + fetchOptions: { + proxy: 'http://localhost:8888', + }, }); ``` -**Delete a custom metadata field** - -delete a custom metadata field as per the [API documentation here](https://docs.imagekit.io/api-reference/custom-metadata-fields-api/delete-custom-metadata-field) - -```js -// Using Callback Function - -imagekit.deleteCustomMetadataField( - "field_id", - function(error, result) { - if(error) console.log(error); - else console.log(result); - } -); - + **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] -// Using Promises +```ts +import ImageKit from 'npm:@imagekit/nodejs'; -imagekit.deleteCustomMetadataField( - "field_id" -).then(response => { - console.log(response); -}).catch(error => { - console.log(error); +const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); +const client = new ImageKit({ + fetchOptions: { + client: httpClient, + }, }); ``` -## Utility functions - -We have included the following commonly used utility functions in this package. - -### Authentication parameter generation - -If you want to implement client-side file upload, you will need a token, expiry timestamp, and a valid signature for that upload. The SDK provides a simple method you can use in your backend code to generate these authentication parameters. - -*Note: The Private API Key should never be exposed in any client-side code. You must always generate these authentication parameters on the server-side* - -```js -var authenticationParameters = imagekit.getAuthenticationParameters(token, expire); -``` - -Returns -```js -{ - token : "unique_token", - expire : "valid_expiry_timestamp", - signature : "generated_signature" -} -``` - -Both the `token` and `expire` parameters are optional. If not specified, the SDK uses the [uuid](https://www.npmjs.com/package/uuid) package to generate a random token and generate a valid expiry timestamp internally. `token` and `expire` are always returned in the response, no matter if they are provided as an input to this method or not. - -### Distance calculation between two pHash values - -Perceptual hashing allows you to construct a hash value that uniquely identifies an input image based on an image's contents. For example, [ImageKit.io metadata API](https://docs.imagekit.io/api-reference/metadata-api) returns the pHash value of an image in the response. You can use this value to [find a duplicate (or similar) image](https://docs.imagekit.io/api-reference/metadata-api#using-phash-to-find-similar-or-duplicate-images) by calculating the distance between the two images' pHash value. - -This SDK exposes the `pHashDistance` function to calculate the distance between two pHash values. It accepts two pHash hexadecimal strings and returns a numeric value indicative of the level of difference between the two images. - -```js -const calculateDistance = () => { - // asynchronously fetch metadata of two uploaded image files - // ... - // Extract pHash strings from both: say 'firstHash' and 'secondHash' - // ... - // Calculate the distance between them: - const distance = imagekit.pHashDistance(firstHash, secondHash); - return distance; -} -``` -#### Distance calculation examples - -```js -imagekit.pHashDistance('f06830ca9f1e3e90', 'f06830ca9f1e3e90'); -// output: 0 (same image) - -imagekit.pHashDistance('2d5ad3936d2e015b', '2d6ed293db36a4fb'); -// output: 17 (similar images) - -imagekit.pHashDistance('a4a65595ac94518b', '7838873e791f8400'); -// output: 37 (dissimilar images) -``` -## Access request-id, other response headers and HTTP status code -You can access `$ResponseMetadata` on success or error object to access the HTTP status code and response headers. - -```javascript -// Success -var response = await imagekit.getPurgeCacheStatus(requestId); -console.log(response.$ResponseMetadata.statusCode); // 200 - -// {'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'} -console.log(response.$ResponseMetadata.headers); - -// Error -try { - await imagekit.getPurgeCacheStatus(requestId); -} catch (ex) { - console.log(response.$ResponseMetadata.statusCode); // 404 - - // {'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'} - console.log(response.$ResponseMetadata.headers); -} -``` - -## Rate limits -Except for upload API, all [ImageKit APIs are rate limited](https://docs.imagekit.io/api-reference/api-introduction/rate-limits) to protect the infrastructure from excessive requests rates and to keep ImageKit.io fast and stable for everyone. +## Frequently Asked Questions -When you exceed the rate limits for an endpoint, you will receive a `429` status code. The Node.js library reads the [rate limiting response headers](https://docs.imagekit.io/api-reference/api-introduction/rate-limits#response-headers-to-understand-rate-limits) provided in the API response and adds these in the error argument of the callback or `.catch` when using promises. Please sleep/pause for the number of milliseconds specified by the value of the `X-RateLimit-Reset` property before making additional requests to that endpoint. +## Semantic versioning -| Property | Description | -|----------|-------------| -| `X-RateLimit-Limit` | The maximum number of requests that can be made to this endpoint in the interval specified by the `X-RateLimit-Interval` response header. | -| `X-RateLimit-Reset` | The amount of time in milliseconds before you can make another request to this endpoint. Pause/sleep your workflow for this duration. | -| `X-RateLimit-Interval` | The duration of interval in milliseconds for which this rate limit was exceeded. | +This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: -## Verify webhook events +1. Changes that only affect static types, without breaking runtime behavior. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ +3. Changes that we do not expect to impact the vast majority of users in practice. -ImageKit sends `x-ik-signature` in the webhook request header, which can be used to verify the authenticity of the webhook request. +We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -Verifying webhook signature is easy with imagekit SDK. All you need is the value of the `x-ik-signature` header, request body, and [webhook secret](https://imagekit.io/dashboard/developer/webhooks) from the ImageKit dashboard. +We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/imagekit-typescript/issues) with questions, bugs, or suggestions. -Here is an example using the express.js server. +## Requirements -```js -const express = require('express'); -const Imagekit = require('imagekit'); - -// Webhook configs -const WEBHOOK_SECRET = 'whsec_...'; // Copy from Imagekit dashboard -const WEBHOOK_EXPIRY_DURATION = 300 * 1000; // 300 seconds for example - -const imagekit = new Imagekit({ - publicKey: 'public_...', - urlEndpoint: 'https://ik.imagekit.io/imagekit_id', - privateKey: 'private_...', -}) - -const app = express(); - -app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { - const signature = req.headers["x-ik-signature"]; - const requestBody = req.body; - let webhookResult; - try { - webhookResult = imagekit.verifyWebhookEvent(requestBody, signature, WEBHOOK_SECRET); - } catch (e) { - // `verifyWebhookEvent` method will throw an error if signature is invalid - console.log(e); - res.status(400).send(`Webhook Error`); - } - - const { timestamp, event } = webhookResult; - - // Check if webhook has expired - if (timestamp + WEBHOOK_EXPIRY_DURATION < Date.now()) { - res.status(400).send(`Webhook Error`); - } +TypeScript >= 4.9 is supported. - // Handle webhook - switch (event.type) { - case 'video.transformation.accepted': - // It is triggered when a new video transformation request is accepted for processing. You can use this for debugging purposes. - break; - case 'video.transformation.ready': - // It is triggered when a video encoding is finished, and the transformed resource is ready to be served. You should listen to this webhook and update any flag in your database or CMS against that particular asset so your application can start showing it to users. - break; - case 'video.transformation.error': - // It is triggered if an error occurs during encoding. Listen to this webhook to log the reason. You should check your origin and URL-endpoint settings if the reason is related to download failure. If the reason seems like an error on the ImageKit side, then raise a support ticket at support@imagekit.io. - break; - default: - // ... handle other event types - console.log(`Unhandled event type ${event.type}`); - } - - // Return a response to acknowledge receipt of the event - res.send(); -}) - -app.listen(3000, () => { - console.log(`Example app listening on port 3000`) -}) -``` +The following runtimes are supported: -## Support +- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) +- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. +- Deno v1.28.0 or higher. +- Bun 1.0 or later. +- Cloudflare Workers. +- Vercel Edge Runtime. +- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). +- Nitro v2.6 or greater. -For any feedback or to report any issues or general implementation support, please reach out to [support@imagekit.io](mailto:support@imagekit.io) +Note that React Native is not supported at this time. -## Links -* [Documentation](https://docs.imagekit.io) -* [Main website](https://imagekit.io) +If you are interested in other runtime environments, please open or upvote an issue on GitHub. -## License +## Contributing -Released under the MIT license. +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..8e64327a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting Security Issues + +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. + +To report a security issue, please contact the Stainless team at security@stainless.com. + +## Responsible Disclosure + +We appreciate the efforts of security researchers and individuals who help us maintain the security of +SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +before making any information public. + +## Reporting Non-SDK Related Security Issues + +If you encounter security issues that are not directly related to SDKs but pertain to the services +or products provided by Image Kit, please follow the respective company's security reporting guidelines. + +### Image Kit Terms and Policies + +Please contact developer@imagekit.io for any questions or concerns regarding the security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/api.md b/api.md new file mode 100644 index 00000000..15beb064 --- /dev/null +++ b/api.md @@ -0,0 +1,220 @@ +# Shared + +Types: + +- BaseOverlay +- ImageOverlay +- Overlay +- OverlayPosition +- OverlayTiming +- SolidColorOverlay +- SolidColorOverlayTransformation +- SrcOptions +- StreamingResolution +- SubtitleOverlay +- SubtitleOverlayTransformation +- TextOverlay +- TextOverlayTransformation +- Transformation +- TransformationPosition +- VideoOverlay + +# CustomMetadataFields + +Types: + +- CustomMetadataField +- CustomMetadataFieldListResponse +- CustomMetadataFieldDeleteResponse + +Methods: + +- client.customMetadataFields.create({ ...params }) -> CustomMetadataField +- client.customMetadataFields.update(id, { ...params }) -> CustomMetadataField +- client.customMetadataFields.list({ ...params }) -> CustomMetadataFieldListResponse +- client.customMetadataFields.delete(id) -> CustomMetadataFieldDeleteResponse + +# Files + +Types: + +- File +- Folder +- Metadata +- FileUpdateResponse +- FileCopyResponse +- FileMoveResponse +- FileRenameResponse +- FileUploadResponse + +Methods: + +- client.files.update(fileID, { ...params }) -> FileUpdateResponse +- client.files.delete(fileID) -> void +- client.files.copy({ ...params }) -> FileCopyResponse +- client.files.get(fileID) -> File +- client.files.move({ ...params }) -> FileMoveResponse +- client.files.rename({ ...params }) -> FileRenameResponse +- client.files.upload({ ...params }) -> FileUploadResponse + +## Bulk + +Types: + +- BulkDeleteResponse +- BulkAddTagsResponse +- BulkRemoveAITagsResponse +- BulkRemoveTagsResponse + +Methods: + +- client.files.bulk.delete({ ...params }) -> BulkDeleteResponse +- client.files.bulk.addTags({ ...params }) -> BulkAddTagsResponse +- client.files.bulk.removeAITags({ ...params }) -> BulkRemoveAITagsResponse +- client.files.bulk.removeTags({ ...params }) -> BulkRemoveTagsResponse + +## Versions + +Types: + +- VersionListResponse +- VersionDeleteResponse + +Methods: + +- client.files.versions.list(fileID) -> VersionListResponse +- client.files.versions.delete(versionID, { ...params }) -> VersionDeleteResponse +- client.files.versions.get(versionID, { ...params }) -> File +- client.files.versions.restore(versionID, { ...params }) -> File + +## Metadata + +Methods: + +- client.files.metadata.get(fileID) -> Metadata +- client.files.metadata.getFromURL({ ...params }) -> Metadata + +# Assets + +Types: + +- AssetListResponse + +Methods: + +- client.assets.list({ ...params }) -> AssetListResponse + +# Cache + +## Invalidation + +Types: + +- InvalidationCreateResponse +- InvalidationGetResponse + +Methods: + +- client.cache.invalidation.create({ ...params }) -> InvalidationCreateResponse +- client.cache.invalidation.get(requestID) -> InvalidationGetResponse + +# Folders + +Types: + +- FolderCreateResponse +- FolderDeleteResponse +- FolderCopyResponse +- FolderMoveResponse +- FolderRenameResponse + +Methods: + +- client.folders.create({ ...params }) -> FolderCreateResponse +- client.folders.delete({ ...params }) -> FolderDeleteResponse +- client.folders.copy({ ...params }) -> FolderCopyResponse +- client.folders.move({ ...params }) -> FolderMoveResponse +- client.folders.rename({ ...params }) -> FolderRenameResponse + +## Job + +Types: + +- JobGetResponse + +Methods: + +- client.folders.job.get(jobID) -> JobGetResponse + +# Accounts + +## Usage + +Types: + +- UsageGetResponse + +Methods: + +- client.accounts.usage.get({ ...params }) -> UsageGetResponse + +## Origins + +Types: + +- OriginRequest +- OriginResponse +- OriginListResponse + +Methods: + +- client.accounts.origins.create({ ...params }) -> OriginResponse +- client.accounts.origins.update(id, { ...params }) -> OriginResponse +- client.accounts.origins.list() -> OriginListResponse +- client.accounts.origins.delete(id) -> void +- client.accounts.origins.get(id) -> OriginResponse + +## URLEndpoints + +Types: + +- URLEndpointRequest +- URLEndpointResponse +- URLEndpointListResponse + +Methods: + +- client.accounts.urlEndpoints.create({ ...params }) -> URLEndpointResponse +- client.accounts.urlEndpoints.update(id, { ...params }) -> URLEndpointResponse +- client.accounts.urlEndpoints.list() -> URLEndpointListResponse +- client.accounts.urlEndpoints.delete(id) -> void +- client.accounts.urlEndpoints.get(id) -> URLEndpointResponse + +# Beta + +## V2 + +### Files + +Types: + +- FileUploadResponse + +Methods: + +- client.beta.v2.files.upload({ ...params }) -> FileUploadResponse + +# Webhooks + +Types: + +- VideoTransformationAcceptedEvent +- VideoTransformationErrorEvent +- VideoTransformationReadyEvent +- UnsafeUnwrapWebhookEvent +- UnwrapWebhookEvent + +Methods: + +- client.webhooks.unsafeUnwrap(body) -> void +- client.webhooks.unwrap(body) -> void diff --git a/babel-register.js b/babel-register.js deleted file mode 100644 index a24dfd20..00000000 --- a/babel-register.js +++ /dev/null @@ -1,3 +0,0 @@ -const register = require("@babel/register").default; - -register({ extensions: [".ts", ".tsx", ".js", ".jsx"] }); diff --git a/bin/publish-npm b/bin/publish-npm new file mode 100644 index 00000000..45e8aa80 --- /dev/null +++ b/bin/publish-npm @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -eux + +npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" + +yarn build +cd dist + +# Get package name and version from package.json +PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" +VERSION="$(jq -r -e '.version' ./package.json)" + +# Get latest version from npm +# +# If the package doesn't exist, npm will return: +# { +# "error": { +# "code": "E404", +# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", +# "detail": "'the_package' is not in this registry..." +# } +# } +NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" + +# Check if we got an E404 error +if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then + # Package doesn't exist yet, no last version + LAST_VERSION="" +elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then + # Report other errors + echo "ERROR: npm returned unexpected data:" + echo "$NPM_INFO" + exit 1 +else + # Success - get the version + LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes +fi + +# Check if current version is pre-release (e.g. alpha / beta / rc) +CURRENT_IS_PRERELEASE=false +if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then + CURRENT_IS_PRERELEASE=true + CURRENT_TAG="${BASH_REMATCH[1]}" +fi + +# Check if last version is a stable release +LAST_IS_STABLE_RELEASE=true +if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then + LAST_IS_STABLE_RELEASE=false +fi + +# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. +if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then + TAG="$CURRENT_TAG" +else + TAG="latest" +fi + +# Publish with the appropriate tag +yarn publish --tag "$TAG" diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..c1a01a62 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,42 @@ +// @ts-check +import tseslint from 'typescript-eslint'; +import unusedImports from 'eslint-plugin-unused-imports'; +import prettier from 'eslint-plugin-prettier'; + +export default tseslint.config( + { + languageOptions: { + parser: tseslint.parser, + parserOptions: { sourceType: 'module' }, + }, + files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'], + ignores: ['dist/'], + plugins: { + '@typescript-eslint': tseslint.plugin, + 'unused-imports': unusedImports, + prettier, + }, + rules: { + 'no-unused-vars': 'off', + 'prettier/prettier': 'error', + 'unused-imports/no-unused-imports': 'error', + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + regex: '^@imagekit/nodejs(/.*)?', + message: 'Use a relative import, not a package import.', + }, + ], + }, + ], + }, + }, + { + files: ['tests/**', 'examples/**', 'packages/**'], + rules: { + 'no-restricted-imports': 'off', + }, + }, +); diff --git a/examples/.keep b/examples/.keep new file mode 100644 index 00000000..0651c89c --- /dev/null +++ b/examples/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store example files demonstrating usage of this SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/index.ts b/index.ts deleted file mode 100644 index 59d984fc..00000000 --- a/index.ts +++ /dev/null @@ -1,681 +0,0 @@ -/* - Helper Modules -*/ -import _ from "lodash"; -import errorMessages from "./libs/constants/errorMessages"; -import { - BulkDeleteFilesError, - BulkDeleteFilesResponse, - CopyFolderError, - CopyFolderResponse, - FileDetailsOptions, - FileObject, - FolderObject, - FileMetadataResponse, - ImageKitOptions, - ListFileOptions, - ListFileResponse, - MoveFolderError, - MoveFolderResponse, - PurgeCacheResponse, - PurgeCacheStatusResponse, - UploadOptions, - UploadResponse, - UrlOptions, - CopyFileOptions, - MoveFileOptions, - CreateFolderOptions, - CopyFolderOptions, - MoveFolderOptions, - FileVersionDetailsOptions, - DeleteFileVersionOptions, - RestoreFileVersionOptions, - CreateCustomMetadataFieldOptions, - GetCustomMetadataFieldsOptions, - CustomMetadataField, - UpdateCustomMetadataFieldOptions, - RenameFileOptions, - RenameFileResponse, -} from "./libs/interfaces"; -import { IKCallback } from "./libs/interfaces/IKCallback"; -import manage from "./libs/manage"; -import signature from "./libs/signature"; -import upload from "./libs/upload"; -import { verify as verifyWebhookEvent } from "./utils/webhook-signature"; -import customMetadataField from "./libs/manage/custom-metadata-field"; -/* - Implementations -*/ -import url from "./libs/url"; -/* - Utils -*/ -import pHashUtils from "./utils/phash"; -import transformationUtils from "./utils/transformation"; -import IKResponse from "./libs/interfaces/IKResponse"; - -const promisify = function (thisContext: ImageKit, fn: Function) { - return function (...args: any[]): Promise | void { - if (args.length === fn.length && typeof args[args.length - 1] !== "undefined") { - if (typeof args[args.length - 1] !== "function") { - throw new Error("Callback must be a function."); - } - fn.call(thisContext, ...args); - } else { - return new Promise((resolve, reject) => { - const callback = function (err: Error, ...results: any[]) { - if (err) { - return reject(err); - } else { - resolve(results.length > 1 ? results : results[0]); - } - }; - args.pop() - args.push(callback); - fn.call(thisContext, ...args); - }); - } - }; -}; -class ImageKit { - options: ImageKitOptions = { - uploadEndpoint: "https://upload.imagekit.io/api/v1/files/upload", - publicKey: "", - privateKey: "", - urlEndpoint: "", - transformationPosition: transformationUtils.getDefault(), - }; - - constructor(opts: ImageKitOptions = {} as ImageKitOptions) { - this.options = _.extend(this.options, opts); - if (!this.options.publicKey) { - throw new Error(errorMessages.MANDATORY_PUBLIC_KEY_MISSING.message); - } - if (!this.options.privateKey) { - throw new Error(errorMessages.MANDATORY_PRIVATE_KEY_MISSING.message); - } - if (!this.options.urlEndpoint) { - throw new Error(errorMessages.MANDATORY_URL_ENDPOINT_KEY_MISSING.message); - } - } - - /** - * This method allows you to create an URL to access a file using the relative or absolute path and the ImageKit URL endpoint (urlEndpoint). The file can be an image, video or any other static file supported by ImageKit. - * - * @see {@link https://github.com/imagekit-developer/imagekit-nodejs#url-generation} - * @see {@link https://docs.imagekit.io/integration/url-endpoints} - * - * @param urlOptions - */ - - url(urlOptions: UrlOptions): string { - return url(urlOptions, this.options); - } - - /** - * You can upload file to ImageKit.io media library from your server-side using private API key authentication. - * - * @see {@link https://docs.imagekit.io/api-reference/upload-file-api/server-side-file-upload} - * - * @param uploadOptions - */ - upload(uploadOptions: UploadOptions): Promise>; - upload(uploadOptions: UploadOptions, callback: IKCallback>): void; - upload( - uploadOptions: UploadOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, upload)(uploadOptions, this.options, callback); - } - - /** - * This API can list all the uploaded files in your ImageKit.io media library. - * For searching and filtering, you can use query parameters as described in docs. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/list-and-search-files} - * - * @param listFilesOptions - */ - listFiles(listOptions: ListFileOptions): Promise>; - listFiles(listOptions: ListFileOptions, callback: IKCallback>): void; - listFiles( - listOptions: ListFileOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.listFiles)(listOptions, this.options, callback); - } - - /** - * Get the file details such as tags, customCoordinates, and isPrivate properties using get file detail API. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/get-file-details} - * - * @param fileId - */ - getFileDetails(fileId: string): Promise>; - getFileDetails(fileId: string, callback: IKCallback>): void; - getFileDetails( - fileId: string, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.getFileDetails)(fileId, this.options, callback); - } - - /** - * Get all versions of an assset. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/get-file-versions} - * - * @param fileId - */ - getFileVersions(fileId: string): Promise>; - getFileVersions(fileId: string, callback: IKCallback>): void; - getFileVersions( - fileId: string, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.getFileVersions)(fileId, this.options, callback); - } - - /** - * Get file details of a specific version. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/get-file-version-details} - * - * @param fileVersionDetailsOptions - */ - getFileVersionDetails(fileVersionDetailsOptions: FileVersionDetailsOptions): Promise>; - getFileVersionDetails( - fileVersionDetailsOptions: FileVersionDetailsOptions, - callback: IKCallback>, - ): void; - getFileVersionDetails( - fileVersionDetailsOptions: FileVersionDetailsOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.getFileVersionDetails)( - fileVersionDetailsOptions, - this.options, - callback, - ); - } - - /** - * Get image exif, pHash and other metadata for uploaded files in ImageKit.io media library using this API. - * - * @see {@link https://docs.imagekit.io/api-reference/metadata-api/get-image-metadata-for-uploaded-media-files} - * - * @param fileIdOrURL The unique fileId of the uploaded file or absolute URL. - */ - getFileMetadata(fileIdOrURL: string): Promise>; - getFileMetadata(fileIdOrURL: string, callback: IKCallback>): void; - getFileMetadata( - fileIdOrURL: string, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.getFileMetadata)( - fileIdOrURL, - this.options, - callback, - ); - } - - /** - * Update file details such as tags and customCoordinates attribute using update file detail API. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/update-file-details} - * - * @param fileId The unique fileId of the uploaded file. fileId is returned in list files API and upload API. - * @param updateData - */ - updateFileDetails(fileId: string, updateData: FileDetailsOptions): Promise>; - updateFileDetails(fileId: string, updateData: FileDetailsOptions, callback: IKCallback>): void; - updateFileDetails( - fileId: string, - updateData: FileDetailsOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.updateFileDetails)( - fileId, - updateData, - this.options, - callback, - ); - } - - /** - * Add tags to multiple files in a single request. The method accepts an array of fileIDs of the files and an array of tags that have to be added to those files. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/add-tags-bulk} - * - * @param fileIds - * @param tags - */ - bulkAddTags(fileIds: string[], tags: string[]): Promise>; - bulkAddTags(fileIds: string[], tags: string[], callback: IKCallback>): void; - bulkAddTags( - fileIds: string[], - tags: string[], - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.bulkAddTags)(fileIds, tags, this.options, callback); - } - - /** - * Remove tags to multiple files in a single request. The method accepts an array of fileIDs of the files and an array of tags that have to be removed to those files. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/remove-tags-bulk} - * - * @param fileIds - * @param tags - */ - bulkRemoveTags(fileIds: string[], tags: string[]): Promise>; - bulkRemoveTags(fileIds: string[], tags: string[], callback: IKCallback>): void; - bulkRemoveTags( - fileIds: string[], - tags: string[], - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.bulkRemoveTags)(fileIds, tags, this.options, callback); - } - - /** - * Remove AITags from multiple files in a single request. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/remove-aitags-bulk} - * - * @param fileIds - * @param tags - */ - bulkRemoveAITags(fileIds: string[], tags: string[]): Promise>; - bulkRemoveAITags(fileIds: string[], tags: string[], callback: IKCallback>): void; - bulkRemoveAITags( - fileIds: string[], - tags: string[], - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.bulkRemoveAITags)(fileIds, tags, this.options, callback); - } - - /** - * You can programmatically delete uploaded files in media library using delete file API. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/delete-file} - * - * @param fileId The unique fileId of the uploaded file. fileId is returned in list files API and upload API - */ - deleteFile(fileId: string): Promise>; - deleteFile(fileId: string, callback: IKCallback>): void; - deleteFile(fileId: string, callback?: IKCallback>): void | Promise> { - return promisify>(this, manage.deleteFile)(fileId, this.options, callback); - } - - /** - * Delete any non-current version of a file. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/delete-file-version} - * - * @param deleteFileVersionOptions - */ - deleteFileVersion(deleteFileVersionOptions: DeleteFileVersionOptions): Promise>; - deleteFileVersion(deleteFileVersionOptions: DeleteFileVersionOptions, callback: IKCallback>): void; - deleteFileVersion( - deleteFileVersionOptions: DeleteFileVersionOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.deleteFileVersion)( - deleteFileVersionOptions, - this.options, - callback, - ); - } - - /** - * Restore file version to a different version of a file. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/restore-file-version} - * - * @param restoreFileVersionOptions - */ - restoreFileVersion(restoreFileVersionOptions: RestoreFileVersionOptions): Promise>; - restoreFileVersion( - restoreFileVersionOptions: RestoreFileVersionOptions, - callback: IKCallback>, - ): void; - restoreFileVersion( - restoreFileVersionOptions: RestoreFileVersionOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.restoreFileVersion)( - restoreFileVersionOptions, - this.options, - callback, - ); - } - - /** - * This will purge CDN and ImageKit.io internal cache. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/purge-cache} - * - * @param url The exact URL of the file to be purged. For example - https://ik.imageki.io/your_imagekit_id/rest-of-the-file-path.jpg - */ - purgeCache(url: string): Promise>; - purgeCache(url: string, callback: IKCallback>): void; - purgeCache( - url: string, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.purgeCache)(url, this.options, callback); - } - - /** - * Get the status of submitted purge request. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/purge-cache-status} - * - * @param requestId The requestId returned in response of purge cache API. - */ - getPurgeCacheStatus(requestId: string, callback: IKCallback>): void; - getPurgeCacheStatus(requestId: string): Promise>; - getPurgeCacheStatus( - requestId: string, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.getPurgeCacheStatus)( - requestId, - this.options, - callback, - ); - } - - /** - * Delete multiple files. The method accepts an array of file IDs of the files that have to be deleted. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/delete-files-bulk} - * - * @param fileIdArray The requestId returned in response of purge cache API. - */ - bulkDeleteFiles( - fileIdArray: string[], - callback?: IKCallback, IKResponse>, - ): void | Promise>; - bulkDeleteFiles( - fileIdArray: string[], - callback?: IKCallback, IKResponse>, - ): void | Promise> { - return promisify>(this, manage.bulkDeleteFiles)( - fileIdArray, - this.options, - callback, - ); - } - - /** - * This will copy a file from one location to another. This method accepts the source file's path and destination folder path. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/copy-file} - * - * @param copyFileOptions - */ - copyFile(copyFileOptions: CopyFileOptions): Promise>; - copyFile(copyFileOptions: CopyFileOptions, callback: IKCallback>): void; - copyFile( - copyFileOptions: CopyFileOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.copyFile)(copyFileOptions, this.options, callback); - } - - /** - * This will move a file from one location to another. This method accepts the source file's path and destination folder path. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-file} - * - * @param moveFileOptions - */ - moveFile(moveFileOptions: MoveFileOptions): Promise>; - moveFile(moveFileOptions: MoveFileOptions, callback: IKCallback>): void; - moveFile( - moveFileOptions: MoveFileOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.moveFile)(moveFileOptions, this.options, callback); - } - - /** - * You can programmatically rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. Note: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/rename-file} - * - * @param renameFileOptions - */ - renameFile(renameFileOptions: RenameFileOptions): Promise>; - renameFile(renameFileOptions: RenameFileOptions, callback: IKCallback>): void; - renameFile( - renameFileOptions: RenameFileOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.renameFile)( - renameFileOptions, - this.options, - callback, - ); - } - - /** - * This will create a new folder. This method accepts folder name and parent folder path. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/create-folder} - * - * @param createFolderOptions - */ - createFolder(createFolderOptions: CreateFolderOptions): Promise>; - createFolder(createFolderOptions: CreateFolderOptions, callback: IKCallback>): void; - createFolder( - createFolderOptions: CreateFolderOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, manage.createFolder)(createFolderOptions, this.options, callback); - } - - /** - * This will delete the specified folder and all nested files & folders. This method accepts the full path of the folder that is to be deleted. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/delete-folder} - * - * @param foldePath - */ - deleteFolder(folderPath: string): Promise>; - deleteFolder(folderPath: string, callback: IKCallback>): void; - deleteFolder(folderPath: string, callback?: IKCallback>): void | Promise> { - return promisify>(this, manage.deleteFolder)(folderPath, this.options, callback); - } - - /** - * This will copy a folder from one location to another. This method accepts the source folder's path and destination folder path. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/copy-folder} - * - * @param copyFolderOptions - */ - copyFolder(copyFolderOptions: CopyFolderOptions): Promise>; - copyFolder( - copyFolderOptions: CopyFolderOptions, - callback: IKCallback, IKResponse>, - ): void; - copyFolder( - copyFolderOptions: CopyFolderOptions, - callback?: IKCallback, IKResponse>, - ): void | Promise> { - return promisify>(this, manage.copyFolder)( - copyFolderOptions, - this.options, - callback, - ); - } - - /** - * This will move a folder from one location to another. This method accepts the source folder's path and destination folder path. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-folder} - * - * @param moveFolderOptions - */ - moveFolder(moveFolderOptions: MoveFolderOptions): Promise>; - moveFolder( - moveFolderOptions: MoveFolderOptions, - callback: IKCallback, IKResponse>, - ): void; - moveFolder( - moveFolderOptions: MoveFolderOptions, - callback?: IKCallback, MoveFolderError>, - ): void | Promise> { - return promisify>(this, manage.moveFolder)( - moveFolderOptions, - this.options, - callback, - ); - } - - /** - * In case you are looking to implement client-side file upload, you are going to need a token, expiry timestamp, and a valid signature for that upload. The SDK provides a simple method that you can use in your code to generate these authentication parameters for you. - * - * @see {@link https://github.com/imagekit-developer/imagekit-nodejs#authentication-parameter-generation} - * - * @param token - * @param expire - */ - getAuthenticationParameters(token?: string, expire?: number): { token: string; expire: number; signature: string } { - return signature.getAuthenticationParameters(token, expire, this.options); - } - - /** - * This allows us to get a bulk operation status e.g. copy or move folder. This method accepts jobId that is returned by copy and move folder operations. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-folder} - * - * @param jobId - */ - getBulkJobStatus(jobId: string): Promise>; - getBulkJobStatus(jobId: string, callback: IKCallback>): Promise>; - getBulkJobStatus(jobId: string, callback?: IKCallback>): void | Promise> { - return promisify>(this, manage.getBulkJobStatus)(jobId, this.options, callback); - } - - /** - * Create custom metadata field - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/create-custom-metadata-field} - * - * @param createCustomMetadataFieldOptions - */ - createCustomMetadataField( - createCustomMetadataFieldOptions: CreateCustomMetadataFieldOptions, - ): Promise>; - createCustomMetadataField( - createCustomMetadataFieldOptions: CreateCustomMetadataFieldOptions, - callback: IKCallback>, - ): Promise>; - createCustomMetadataField( - createCustomMetadataFieldOptions: CreateCustomMetadataFieldOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, customMetadataField.create)( - createCustomMetadataFieldOptions, - this.options, - callback, - ); - } - - /** - *Get a list of all the custom metadata fields. - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/get-custom-metadata-field} - * - */ - getCustomMetadataFields( - getCustomMetadataFieldsOptions: GetCustomMetadataFieldsOptions, - ): Promise>; - getCustomMetadataFields( - getCustomMetadataFieldsOptions: GetCustomMetadataFieldsOptions, - callback: IKCallback>, - ): Promise>; - getCustomMetadataFields( - getCustomMetadataFieldsOptions: GetCustomMetadataFieldsOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, customMetadataField.list)( - getCustomMetadataFieldsOptions, - this.options, - callback, - ); - } - - /** - * Update custom metadata field - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/update-custom-metadata-field} - * - * @param fieldId - * @param updateCustomMetadataFieldOptions - */ - updateCustomMetadataField( - fieldId: string, - updateCustomMetadataFieldOptions: UpdateCustomMetadataFieldOptions, - ): Promise>; - updateCustomMetadataField( - fieldId: string, - updateCustomMetadataFieldOptions: UpdateCustomMetadataFieldOptions, - callback: IKCallback>, - ): Promise>; - updateCustomMetadataField( - fieldId: string, - updateCustomMetadataFieldOptions: UpdateCustomMetadataFieldOptions, - callback?: IKCallback>, - ): void | Promise> { - return promisify>(this, customMetadataField.update)( - fieldId, - updateCustomMetadataFieldOptions, - this.options, - callback, - ); - } - - /** - * Delete a custom metadata field - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/delete-custom-metadata-field} - * - * @param fieldId - */ - deleteCustomMetadataField(fieldId: string): Promise>; - deleteCustomMetadataField(fieldId: string, callback: IKCallback>): void; - deleteCustomMetadataField(fieldId: string, callback?: IKCallback>): void | Promise> { - return promisify>(this, customMetadataField.deleteField)(fieldId, this.options, callback); - } - - /** - * Perceptual hashing allows you to construct a hash value that uniquely identifies an input image based on an image's contents. ImageKit.io metadata API returns the pHash value of an image in the response. You can use this value to find a duplicate (or similar) image by calculating the distance between the two images' pHash value. - * - * This SDK exposes pHashDistance function to calculate the distance between two pHash values. It accepts two pHash hexadecimal strings and returns a numeric value indicative of the level of difference between the two images. - * - * @see {@link https://docs.imagekit.io/api-reference/metadata-api#perceptual-hash-phash} - * - * @param firstPHash - * @param secondPHash - */ - pHashDistance(firstPHash: string, secondPHash: string): number | Error { - return pHashUtils.pHashDistance(firstPHash, secondPHash); - } - - /** - * @param payload - Raw webhook request body (Encoded as UTF8 string or Buffer) - * @param signature - Webhook signature as UTF8 encoded strings (Stored in `x-ik-signature` header of the request) - * @param secret - Webhook secret as UTF8 encoded string [Copy from ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks) - * @returns \{ `timestamp`: Verified UNIX epoch timestamp if signature, `event`: Parsed webhook event payload \} - */ - verifyWebhookEvent = verifyWebhookEvent; -} - -export = ImageKit; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..f7631d79 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,23 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^@imagekit/nodejs$': '/src/index.ts', + '^@imagekit/nodejs/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: [ + '/ecosystem-tests/', + '/dist/', + '/deno/', + '/deno_tests/', + '/packages/', + ], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/libs/constants/errorMessages.ts b/libs/constants/errorMessages.ts deleted file mode 100644 index ba10e6cd..00000000 --- a/libs/constants/errorMessages.ts +++ /dev/null @@ -1,53 +0,0 @@ -export default { - "MANDATORY_INITIALIZATION_MISSING": { message: "Missing publicKey or privateKey or urlEndpoint during ImageKit initialization", help: "" }, - "MANDATORY_PUBLIC_KEY_MISSING": { message: "Missing publicKey during ImageKit initialization", help: "" }, - "MANDATORY_PRIVATE_KEY_MISSING": { message: "Missing privateKey during ImageKit initialization", help: "" }, - "MANDATORY_URL_ENDPOINT_KEY_MISSING": { message: "Missing urlEndpoint during ImageKit initialization", help: "" }, - "INVALID_TRANSFORMATION_POSITION": { message: "Invalid transformationPosition parameter", help: "" }, - "CACHE_PURGE_URL_MISSING": { message: "Missing URL parameter for this request", help: "" }, - "CACHE_PURGE_STATUS_ID_MISSING": { message: "Missing Request ID parameter for this request", help: "" }, - "FILE_ID_MISSING": { message: "Missing fileId parameter for this request", help: "" }, - "FILE_VERSION_ID_MISSING": { message: "Missing versionId parameter for this request", help: "" }, - "FILE_ID_OR_URL_MISSING": { message: "Pass either fileId or remote URL of the image as first parameter", help: "" }, - "INVALID_LIST_OPTIONS": { message: "Pass a valid JSON list options e.g. {skip: 10, limit: 100}.", help: "" }, - "UPDATE_DATA_MISSING": { message: "Missing file update data for this request", help: "" }, - "UPDATE_DATA_TAGS_INVALID": { message: "Invalid tags parameter for this request", help: "tags should be passed as null or an array like ['tag1', 'tag2']" }, - "UPDATE_DATA_COORDS_INVALID": { message: "Invalid customCoordinates parameter for this request", help: "customCoordinates should be passed as null or a string like 'x,y,width,height'" }, - "LIST_FILES_INPUT_MISSING": { message: "Missing options for list files", help: "If you do not want to pass any parameter for listing, pass an empty object" }, - "MISSING_UPLOAD_DATA": { message: "Missing data for upload", help: "" }, - "MISSING_UPLOAD_FILE_PARAMETER": { message: "Missing file parameter for upload", help: "" }, - "MISSING_UPLOAD_FILENAME_PARAMETER": { message: "Missing fileName parameter for upload", help: "" }, - "JOB_ID_MISSING": { message: "Missing jobId parameter", help: "" }, - "INVALID_DESTINATION_FOLDER_PATH": { message: "Invalid destinationPath value", help: "It should be a string like '/path/to/folder'" }, - "INVALID_INCLUDE_VERSION": { message: "Invalid includeFileVersions value", help: "It should be a boolean" }, - "INVALID_SOURCE_FILE_PATH": { message: "Invalid sourceFilePath value", help: "It should be a string like /path/to/file.jpg'" }, - "INVALID_SOURCE_FOLDER_PATH": { message: "Invalid sourceFolderPath value", help: "It should be a string like '/path/to/folder'" }, - "INVALID_FOLDER_NAME": { message: "Invalid folderName value", help: "" }, - "INVALID_PARENT_FOLDER_PATH": { message: "Invalid parentFolderPath value", help: "It should be a string like '/path/to/folder'" }, - "INVALID_FOLDER_PATH": { message: "Invalid folderPath value", help: "It should be a string like '/path/to/folder'" }, - // pHash errors - "INVALID_PHASH_VALUE": { message: "Invalid pHash value", help: "Both pHash strings must be valid hexadecimal numbers" }, - "MISSING_PHASH_VALUE": { message: "Missing pHash value", help: "Please pass two pHash values" }, - "UNEQUAL_STRING_LENGTH": { message: "Unequal pHash string length", help: "For distance calucation, the two pHash strings must have equal length" }, - //bulk delete errors - "INVALID_FILEIDS_VALUE": { message: "Invalid value for fileIds", help: "fileIds should be an array of fileId of the files. The array should have atleast one fileId." }, - "BULK_ADD_TAGS_INVALID": { message: "Invalid value for tags", help: "tags should be a non empty array of string like ['tag1', 'tag2']." }, - "BULK_AI_TAGS_INVALID": { message: "Invalid value for AITags", help: "AITags should be a non empty array of string like ['tag1', 'tag2']." }, - "CMF_NAME_MISSING": { message: "Missing name parameter for this request", help: "" }, - "CMF_LABEL_MISSING": { message: "Missing label parameter for this request", help: "" }, - "CMF_SCHEMA_MISSING": { message: "Missing schema parameter for this request", help: "" }, - "CMF_SCHEMA_INVALID": { message: "Invalid value for schema", help: "schema should have a mandatory type field." }, - "CMF_LABEL_SCHEMA_MISSING": { message: "Both label and schema is missing", help: "" }, - "CMF_FIELD_ID_MISSING": { message: "Missing fieldId parameter for this request", help: "" }, - "INVALID_FILE_PATH": { message: "Invalid value for filePath", help: "Pass the full path of the file. For example - /path/to/file.jpg" }, - "INVALID_NEW_FILE_NAME": { message: "Invalid value for newFileName. It should be a string.", help: "" }, - "INVALID_PURGE_CACHE": { message: "Invalid value for purgeCache. It should be boolean.", help: "" }, - // Webhook signature - "VERIFY_WEBHOOK_EVENT_SIGNATURE_INCORRECT": { message: "Incorrect signature", help: "Please pass x-ik-signature header as utf8 string" }, - "VERIFY_WEBHOOK_EVENT_SIGNATURE_MISSING": { message: "Signature missing", help: "Please pass x-ik-signature header as utf8 string" }, - "VERIFY_WEBHOOK_EVENT_TIMESTAMP_MISSING": { message: "Timestamp missing", help: "Please pass x-ik-signature header as utf8 string" }, - "VERIFY_WEBHOOK_EVENT_TIMESTAMP_INVALID": { message: "Timestamp invalid", help: "Please pass x-ik-signature header as utf8 string" }, - "INVALID_TRANSFORMATION": { message: "Invalid transformation parameter. Please include at least pre, post, or both.", help: ""}, - "INVALID_PRE_TRANSFORMATION": { message: "Invalid pre transformation parameter.", help: ""}, - "INVALID_POST_TRANSFORMATION": { message: "Invalid post transformation parameter.", help: ""}, -}; \ No newline at end of file diff --git a/libs/constants/supportedTransforms.ts b/libs/constants/supportedTransforms.ts deleted file mode 100644 index fb0e6498..00000000 --- a/libs/constants/supportedTransforms.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @see {@link https://docs.imagekit.io/features/image-transformations} - */ -const supportedTransforms = { - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#width-w} - */ - width: "w", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#height-h} - */ - height: "h", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#aspect-ratio-ar} - */ - aspectRatio: "ar", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#quality-q} - */ - quality: "q", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#crop-crop-modes-and-focus} - */ - crop: "c", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#crop-crop-modes-and-focus} - */ - cropMode: "cm", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#focus-fo} - */ - focus: "fo", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#examples-focus-using-cropped-image-coordinates} - */ - x: "x", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#examples-focus-using-cropped-image-coordinates} - */ - y: "y", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#format-f} - */ - format: "f", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#radius-r} - */ - radius: "r", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#background-color-bg} - */ - background: "bg", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#border-b} - */ - border: "b", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#rotate-rt} - */ - rotation: "rt", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#rotate-rt} - */ - rotate: "rt", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#blur-bl} - */ - blur: "bl", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#named-transformation-n} - */ - named: "n", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#progressive-image-pr} - */ - progressive: "pr", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#lossless-webp-and-png-lo} - */ - lossless: "lo", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#trim-edges-t} - */ - trim: "t", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#image-metadata-md} - */ - metadata: "md", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#color-profile-cp} - */ - colorProfile: "cp", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#default-image-di} - */ - defaultImage: "di", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#dpr-dpr} - */ - dpr: "dpr", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation#sharpen-e-sharpen} - */ - effectSharpen: "e-sharpen", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation#unsharp-mask-e-usm} - */ - effectUSM: "e-usm", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation#contrast-stretch-e-contrast} - */ - effectContrast: "e-contrast", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#grayscale-e-grayscale} - */ - effectGray: "e-grayscale", - - /** - * @link https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation#shadow-e-shadow - */ - effectShadow: "e-shadow", - - /** - * @link https://docs.imagekit.io/features/image-transformations/image-enhancement-and-color-manipulation#gradient-e-gradient - */ - effectGradient: "e-gradient", - - /** - * @see {@link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#original-image-orig} - */ - original: "orig", -}; - -export default supportedTransforms as { [key: string]: string }; -export type SupportedTransformsParam = keyof typeof supportedTransforms; diff --git a/libs/interfaces/BulkDeleteFiles.ts b/libs/interfaces/BulkDeleteFiles.ts deleted file mode 100644 index 9329db3f..00000000 --- a/libs/interfaces/BulkDeleteFiles.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Response when deleting multiple files from the media library. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/delete-files-bulk} - */ -export interface BulkDeleteFilesResponse { - /** - * List of file ids of successfully deleted files - */ - successfullyDeletedFileIds: string[]; -} - -export interface BulkDeleteFilesError extends Error { - help: string; - missingFileIds: string[]; -} diff --git a/libs/interfaces/CopyFile.ts b/libs/interfaces/CopyFile.ts deleted file mode 100644 index eef7cab3..00000000 --- a/libs/interfaces/CopyFile.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface CopyFileOptions { - /** - * The full path of the file you want to copy. For example - /path/to/file.jpg - */ - sourceFilePath: string; - /** - * Full path to the folder you want to copy the above file into. For example - /folder/to/copy/into/ - */ - destinationPath: string; - /** - * Option to copy all versions of a file. By default, only the current version of the file is copied. When set to true, all versions of the file will be copied. - * Default value is false - */ - includeFileVersions?: boolean; -} \ No newline at end of file diff --git a/libs/interfaces/CopyFolder.ts b/libs/interfaces/CopyFolder.ts deleted file mode 100644 index c29d1a0f..00000000 --- a/libs/interfaces/CopyFolder.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Response when copying folder in media library. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/copy-folder} - * - * On success, you will receive a jobId which can be used to get the copy operation's status. - */ -export interface CopyFolderResponse { - jobId: string; -} - -/** - * Error when copying folder in media library. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/copy-folder} - * - * If no files or folders are found at the specified sourceFolderPath then a error is returned. - */ -export interface CopyFolderError extends Error { - help: string; - message: string; - reason: string; -} - -/** - * Copy folder API options - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/copy-folder} - */ -export interface CopyFolderOptions { - /** - * The full path to the source folder you want to copy. For example - /path/of/source/folder. - */ - sourceFolderPath: string; - /** - * Full path to the destination folder where you want to copy the source folder into. For example - /path/of/destination/folder. - */ - destinationPath: string; - /** - * Option to copy all versions of files that are nested inside the selected folder. By default, only the current version of each file will be copied. When set to true, all versions of each file will be copied. - * Default value - false - */ - includeFileVersions?: boolean; -} \ No newline at end of file diff --git a/libs/interfaces/CreateFolder.ts b/libs/interfaces/CreateFolder.ts deleted file mode 100644 index 6f7c0025..00000000 --- a/libs/interfaces/CreateFolder.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface CreateFolderOptions { - /** - * The folder will be created with this name. All characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) will be replaced by an underscore i.e. _. - */ - folderName: string; - /** - * The folder where the new folder should be created, for root use / else the path e.g. containing/folder/. - * Note: If any folder(s) is not present in the parentFolderPath parameter, it will be automatically created. For example, if you pass /product/images/summer, then product, images, and summer folders will be created if they don't already exist. - */ - parentFolderPath: string; -} \ No newline at end of file diff --git a/libs/interfaces/CustomMetatadaField.ts b/libs/interfaces/CustomMetatadaField.ts deleted file mode 100644 index 7efcee06..00000000 --- a/libs/interfaces/CustomMetatadaField.ts +++ /dev/null @@ -1,123 +0,0 @@ -type RequiredSchema = { - isValueRequired: true; - defaultValue: T; -} | { - isValueRequired?: false; - defaultValue?: T; -}; - -type CustomMetadataTextField = RequiredSchema & { - type: "Text"; - minLength?: number; - maxLength?: number; -}; - -type CustomMetadataTextareaField = RequiredSchema & { - type: "Textarea"; - minLength?: number; - maxLength?: number; -}; - -type CustomMetadataNumberField = RequiredSchema & { - type: "Number"; - minValue?: string | number; - maxValue?: string | number; -}; - -type CustomMetadataDateField = RequiredSchema & { - type: "Date"; - minValue?: string | number; - maxValue?: string | number; -}; - -type CustomMetadataBooleanField = RequiredSchema & { - type: "Boolean"; -}; - -type CustomMetadataSingleSelectField = RequiredSchema> & { - type: "SingleSelect"; - selectOptions: Array; -}; - -type CustomMetadataMultiSelectField = RequiredSchema> & { - type: "MultiSelect"; - selectOptions: Array; -}; - -export type CustomMetadataFieldSchema = - | CustomMetadataTextField - | CustomMetadataTextareaField - | CustomMetadataNumberField - | CustomMetadataDateField - | CustomMetadataBooleanField - | CustomMetadataSingleSelectField - | CustomMetadataMultiSelectField; - -export type CustomMetadataFieldSchemaMinusType = - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit; - -/** - * Create a new custom metadata field - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/create-custom-metadata-field} - */ -export interface CreateCustomMetadataFieldOptions { - /** - * Name of the metadata field, unique across all (deleted or not deleted) custom metadata fields. - */ - name: string; - /** - * Label of the metadata field, unique across all non deleted custom metadata fields - */ - label: string; - /** - * An object that describes the rules for the custom metadata key. - */ - schema: CustomMetadataFieldSchema -} - -export interface CustomMetadataField { - id: string; - /** - * Name of the metadata field, unique across all (deleted or not deleted) custom metadata fields. - */ - name: string; - /** - * Label of the metadata field, unique across all non deleted custom metadata fields - */ - label: string; - /** - * An object that describes the rules for the custom metadata key. - */ - schema: CustomMetadataFieldSchema -} - -/** - * Update the label or schema of an existing custom metadata field. - * - * @see {@link https://docs.imagekit.io/api-reference/custom-metadata-fields-api/update-custom-metadata-field} - */ -export interface UpdateCustomMetadataFieldOptions { - /** - * Label of the metadata field, unique across all non deleted custom metadata fields. This parameter is required if schema is not provided. - */ - label?: string; - /** - * An object that describes the rules for the custom metadata key. This parameter is required if label is not provided. - * Note: type cannot be updated and will be ignored if sent with the schema. The schema will be validated as per the existing type. - */ - schema?: CustomMetadataFieldSchemaMinusType -} - -export interface GetCustomMetadataFieldsOptions { - /** - * Set it to true if you want to receive deleted fields as well in the API response. - */ - includeDeleted?: boolean; -} \ No newline at end of file diff --git a/libs/interfaces/FileDetails.ts b/libs/interfaces/FileDetails.ts deleted file mode 100644 index 358ac538..00000000 --- a/libs/interfaces/FileDetails.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { FileType } from "./FileType"; - -export interface EmbeddedMetadataValues { - [key: string]: - | string - | number - | boolean - | Date - | Array -} - -export interface AITagItem { - name: string - confidence: number - source: 'google-auto-tagging' | 'aws-auto-tagging' -} - -export interface CMValues { - [key: string]: | string - | number - | boolean - | Array -} - -interface BgRemoval { - name: string - options: { - bg_color?: string - bg_image_url?: string - add_shadow: boolean - semitransparency: boolean - } -} - -interface AutoTag { - name: string - maxTags: number - minConfidence: number -} - -export type Extension = (BgRemoval | AutoTag)[]; - -/** - * Options when updating file details such as tags and customCoordinates attribute using update file detail API. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/update-file-details} - */ -export interface FileDetailsOptions { - /** - * Array of tags associated with the file. - */ - tags?: string[]; - /** - * Define an important area in the image. - * Example - 50,50,500,500 - */ - customCoordinates?: string; - /* - * Object with array of extensions to be processed on the image. - */ - extensions?: Extension; - /* - * Final status of pending extensions will be sent to this URL. - */ - webhookUrl?: string - /* - * Array of AI tags to remove from the asset. - */ - removeAITags?: string[]; - /* - * A key-value data to be associated with the asset. To unset a key, send null value for that key. Before setting any custom metadata on an asset you have to create the field using custom metadata fields API. - */ - customMetadata?: CMValues; - /** - * Configure the publication status of a file and its versions. - */ - publish?: { - isPublished: boolean; - includeFileVersions?: boolean; - }; -} - -/** - * - * File object. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api#file-object-structure} - */ -export interface FileObject { - /** - * The unique fileId of the uploaded file. - */ - fileId: string; - /** - * Type of item. It can be either file, file-version or folder. - */ - type: "file" | "file-version"; - /** - * Name of the file or folder. - */ - name: string; - /** - * The relative path of the file. In case of image, you can use this - * path to construct different transformations. - */ - filePath: string; - /** - * Array of tags associated with the image. If no tags are set, it will be null. - */ - tags?: string[] | null; - /** - * Is the file marked as private. It can be either true or false. - */ - isPrivateFile: boolean; - /** - * Value of custom coordinates associated with the image in format x,y,width,height. - * If customCoordinates are not defined then it is null. - */ - customCoordinates: string | null; - /** - * A publicly accessible URL of the file. - */ - url: string; - /** - * In case of an image, a small thumbnail URL. - */ - thumbnail: string; - /** - * The type of file, it could be either image or non-image. - */ - fileType: FileType; - /* - * AITags field is populated only because the google-auto-tagging extension was executed synchronously and it received a successresponse. - */ - AITags?: AITagItem[]; - /* - * Field object which will contain the status of each extension at the time of completion of the update/upload request. - */ - extensionStatus?: { [key: string]: string } - /* - * Consolidated embedded metadata associated with the file. It includes exif, iptc, and xmp data. - */ - embeddedMetadata?: EmbeddedMetadataValues | null; - /* - * A key-value data associated with the asset. Before setting any custom metadata on an asset, you have to create the field using custom metadata fields API. - */ - customMetadata?: CMValues; - /* - * Size of the file in bytes - */ - size: number; - /* - * The date and time when the file was first uploaded. The format is YYYY-MM-DDTHH:mm:ss.sssZ - */ - createdAt: string; - /* - * The date and time when the file was last updated. The format is YYYY-MM-DDTHH:mm:ss.sssZ - */ - updatedAt: string; - /* - * Height of the image in pixels (Only for images) - */ - height: number; - /* - * Width of the image in pixels (Only for Images) - */ - width: number; - /* - * A boolean indicating if the image has an alpha layer or not. - */ - hasAlpha: boolean; - /* - * MIME Type of the file. For example - image/jpeg - */ - mime?: string; - /** - * An object containing the file or file version's id (versionId) and name. - */ - versionInfo?: { name: string; id: string }; -} - -/** - * - * Folder object. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api#file-object-structure} - */ -export interface FolderObject { - /** - * The unique fileId of the folder. - */ - folderId: string; - /** - * Type of item. It can be either file, file-version or folder. - */ - type: "folder"; - /** - * Name of the file or folder. - */ - name: string; - /** - * The relative path of the folder. - */ - folderPath: string; - /* - * The date and time when the folder was first created. The format is YYYY-MM-DDTHH:mm:ss.sssZ - */ - createdAt: string; - /* - * The date and time when the folder was last updated. The format is YYYY-MM-DDTHH:mm:ss.sssZ - */ - updatedAt: string; -} - -export interface FileVersionDetailsOptions { - /** - * The unique fileId of the uploaded file. fileId is returned in list files API and upload API. - */ - fileId: string; - /** - * The unique versionId of the uploaded file's version. This is returned in list files API and upload API as id within the versionInfo parameter. - */ - versionId: string; -} diff --git a/libs/interfaces/FileFormat.ts b/libs/interfaces/FileFormat.ts deleted file mode 100644 index aaa9bc90..00000000 --- a/libs/interfaces/FileFormat.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @see {@link https://help.imagekit.io/en/articles/2434102-image-format-support-in-imagekit-for-resizing-compression-and-static-file-delivery} - */ -export type FileFormat = - | "jpg" - | "png" - | "gif" - | "svg" - | "webp" - | "pdf" - | "js" - | "css" - | "txt" - | "mp4" - | "webm" - | "mov" - | "swf" - | "ts" - | "m3u8" - | string; diff --git a/libs/interfaces/FileMetadata.ts b/libs/interfaces/FileMetadata.ts deleted file mode 100644 index 642310fe..00000000 --- a/libs/interfaces/FileMetadata.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { FileFormat } from "./FileFormat"; - -/** - * Response when getting image exif, pHash and other metadata for uploaded files in ImageKit.io media library using this API. - * - * @see {@link https://docs.imagekit.io/api-reference/metadata-api/get-image-metadata-for-uploaded-media-files} - */ -export interface FileMetadataResponse { - height: number; - width: number; - size: number; - format: FileFormat; - hasColorProfile: boolean; - quality: number; - density: number; - hasTransparency: boolean; - /** - * @see {@link https://docs.imagekit.io/api-reference/metadata-api#perceptual-hash-phash} - */ - pHash: string; - /** - * @see {@link https://docs.imagekit.io/api-reference/metadata-api#exif} - */ - exif: { - image: { - Make: string; - Model: string; - Orientation: number; - XResolution: number; - YResolution: number; - ResolutionUnit: number; - Software: string; - ModifyDate: string; - YCbCrPositioning: number; - ExifOffset: number; - GPSInfo: number; - }; - thumbnail: { - Compression: number; - XResolution: number; - YResolution: number; - ResolutionUnit: number; - ThumbnailOffset: number; - ThumbnailLength: number; - }; - exif: { - ExposureTime: number; - FNumber: number; - ExposureProgram: number; - ISO: number; - ExifVersion: string; - DateTimeOriginal: string; - CreateDate: string; - ShutterSpeedValue: number; - ApertureValue: number; - ExposureCompensation: number; - MeteringMode: number; - Flash: number; - FocalLength: number; - SubSecTime: string; - SubSecTimeOriginal: string; - SubSecTimeDigitized: string; - FlashpixVersion: string; - ColorSpace: number; - ExifImageWidth: number; - ExifImageHeight: number; - InteropOffset: number; - FocalPlaneXResolution: number; - FocalPlaneYResolution: number; - FocalPlaneResolutionUnit: number; - CustomRendered: number; - ExposureMode: number; - WhiteBalance: number; - SceneCaptureType: number; - }; - gps: { - GPSVersionID: number[]; - }; - interoperability: { - InteropIndex: string; - InteropVersion: string; - }; - makernote: { [key: string]: string }; - }; -} diff --git a/libs/interfaces/FileType.ts b/libs/interfaces/FileType.ts deleted file mode 100644 index f6b6cb70..00000000 --- a/libs/interfaces/FileType.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Type of files to include in result set. Accepts three values: - * all - include all types of files in result set - * image - only search in image type files - * non-image - only search in files which are not image, e.g., JS or CSS or video files. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/list-and-search-files} - */ -export type FileType = "all" | "image" | "non-image"; diff --git a/libs/interfaces/FileVersion.ts b/libs/interfaces/FileVersion.ts deleted file mode 100644 index b2c489e1..00000000 --- a/libs/interfaces/FileVersion.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface DeleteFileVersionOptions { - /** - * The unique fileId of the uploaded file. fileId is returned in list files API and upload API. - */ - fileId: string; - /** - * The unique versionId of the uploaded file's version. This is returned in list files API and upload API as id within the versionInfo parameter. - */ - versionId: string; -} - -export interface RestoreFileVersionOptions { - /** - * The unique fileId of the uploaded file. fileId is returned in list files API and upload API. - */ - fileId: string; - /** - * The unique versionId of the uploaded file's version. This is returned in list files API and upload API as id within the versionInfo parameter. - */ - versionId: string; -} \ No newline at end of file diff --git a/libs/interfaces/IKCallback.ts b/libs/interfaces/IKCallback.ts deleted file mode 100644 index b33ae426..00000000 --- a/libs/interfaces/IKCallback.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IKCallback { - (err: E | null, response: T): void; - (err: E, response: T | null): void; -} diff --git a/libs/interfaces/IKResponse.ts b/libs/interfaces/IKResponse.ts deleted file mode 100644 index a53ca4fb..00000000 --- a/libs/interfaces/IKResponse.ts +++ /dev/null @@ -1,10 +0,0 @@ -interface ResponseMetadata { - statusCode: number; - headers: Record; -} - -type IKResponse = T extends Error - ? T & { $ResponseMetadata?: ResponseMetadata } - : T & { $ResponseMetadata: ResponseMetadata }; - -export default IKResponse; diff --git a/libs/interfaces/ImageKitOptions.ts b/libs/interfaces/ImageKitOptions.ts deleted file mode 100644 index 122a4de2..00000000 --- a/libs/interfaces/ImageKitOptions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TransformationPosition } from "."; - -export interface ImageKitOptions { - uploadEndpoint?: string, - publicKey: string; - privateKey: string; - urlEndpoint: string; - transformationPosition?: TransformationPosition; -} diff --git a/libs/interfaces/ListFile.ts b/libs/interfaces/ListFile.ts deleted file mode 100644 index bba9321d..00000000 --- a/libs/interfaces/ListFile.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { FileObject, FolderObject } from "./FileDetails"; -import { FileType } from "./FileType"; - -/** - * List and search files options - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/list-and-search-files} - */ - -export interface ListFileOptions { - /** - * Folder path if you want to limit the search within a specific folder. For example, /sales-banner/ will only search in folder sales-banner. - */ - path?: string; - /** - * Type of files to include in result set. Accepts three values: - * all - include all types of files in result set - * image - only search in image type files - * non-image - only search in files which are not image, e.g., JS or CSS or video files. - */ - fileType?: FileType; - /** - * Comma-separated list of tags. Files matching any of the tags are included in result response. If no tag is matched, the file is not included in result set. - */ - tags?: string | string[]; - /** - * Whether to include folders in search results or not. By default only files are searched. - * Accepts true and false. If this is set to true then tags and fileType parameters are ignored. - */ - includeFolder?: boolean; - /** - * The name of the file or folder. - */ - name?: string; - /** - * The maximum number of results to return in response: - * Minimum value - 1 - * Maximum value - 1000 - * Default value - 1000 - */ - limit?: number; - /** - * The number of results to skip before returning results. - * Minimum value - 0 - * Default value - 0 - */ - skip?: number; - /** - * You can sort based on the following fields: - * - name - ASC_NAME or DESC_NAME - * - createdAt - ASC_CREATED or DESC_CREATED - * - updatedAt - ASC_UPDATED or DESC_UPDATED - * - height - ASC_HEIGHT or DESC_HEIGHT - * - width - ASC_WIDTH or DESC_WIDTH - * - size - ASC_SIZE or DESC_SIZE - */ - sort?: string; - /** - * Limit search to either file or folder. Pass all to include both files and folders in search results. - * Default value - `file` - */ - type?: string; - /** - * Query string in a Lucene-like query language. Learn more about the query expression later in this section. - * Note: When the searchQuery parameter is present, the following query parameters will have no effect on the result: - * 1. tags - * 2. type - * 3. name - */ - searchQuery?: string; -} - -/** - * - * List and search response - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/list-and-search-files#response-structure-and-status-code-application-json} - */ -export type ListFileResponse = Array; diff --git a/libs/interfaces/MoveFile.ts b/libs/interfaces/MoveFile.ts deleted file mode 100644 index 1a26a162..00000000 --- a/libs/interfaces/MoveFile.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Move file API options - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-file} - */ -export interface MoveFileOptions { - /** - * The full path of the file you want to move. For example - /path/to/file.jpg - */ - sourceFilePath: string; - /** - * Full path to the folder you want to move the above file into. For example - /folder/to/move/into/ - */ - destinationPath: string; -} \ No newline at end of file diff --git a/libs/interfaces/MoveFolder.ts b/libs/interfaces/MoveFolder.ts deleted file mode 100644 index 2361a6ed..00000000 --- a/libs/interfaces/MoveFolder.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Response when moving folder in media library. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-folder} - * - * On success, you will receive a jobId which can be used to get the move operation's status. - */ -export interface MoveFolderResponse { - jobId: string; -} - -/** - * Error when moving folder in media library. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-folder} - * - * If no files or folders are found at specified sourceFolderPath then a error is returned. - */ -export interface MoveFolderError extends Error { - help: string; - message: string; - reason: string; -} - - -/** - * Move folder API options - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/move-folder} - */ -export interface MoveFolderOptions { - /** - * The full path to the source folder you want to move. For example - /path/of/source/folder. - */ - sourceFolderPath: string; - /** - * Full path to the destination folder where you want to move the source folder into. For example - /path/of/destination/folder. - */ - destinationPath: string; -} \ No newline at end of file diff --git a/libs/interfaces/PurgeCache.ts b/libs/interfaces/PurgeCache.ts deleted file mode 100644 index c7b0e386..00000000 --- a/libs/interfaces/PurgeCache.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Response when purging CDN and ImageKit.io internal cache - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/purge-cache#response-structure-and-status-code} - */ - -export interface PurgeCacheResponse { - /** - * requestId can be used to fetch the status of submitted purge request. - */ - requestId: string; -} - -/** - * Response when getting the status of submitted purge request. - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/purge-cache-status#understanding-response} - */ - -export interface PurgeCacheStatusResponse { - /** - * Pending - The request has been successfully submitted, and purging is in progress. - * Complete - The purge request has been successfully completed. And now you should get a fresh object. - * Check the Age header in response to confirm this. - */ - status: "Pending" | "Completed"; -} diff --git a/libs/interfaces/Rename.ts b/libs/interfaces/Rename.ts deleted file mode 100644 index 98b338df..00000000 --- a/libs/interfaces/Rename.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Response when rename file - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/rename-file} - */ -export interface RenameFileResponse { - /** - * When purgeCache is set to true - */ - purgeRequestId?: string; -} - -/** - * Response when rename file - * - * @see {@link https://docs.imagekit.io/api-reference/media-api/rename-file} - */ -export interface RenameFileOptions { - /** - * The full path of the file you want to rename. For example - /path/to/file.jpg - */ - filePath: string; - /** - * The new name of the file. A filename can contain: - - Alphanumeric Characters: a-z, A-Z, 0-9 (including Unicode letters, marks, and numerals in other languages). - - Special Characters: ., _, and -. Any other character, including space, will be replaced by _. - */ - newFileName: string - /** - * Option to purge cache for the old file URL. When set to true, it will internally issue a purge cache request on CDN to remove cached content on the old URL. - */ - purgeCache: boolean -} diff --git a/libs/interfaces/Transformation.ts b/libs/interfaces/Transformation.ts deleted file mode 100644 index a2990f66..00000000 --- a/libs/interfaces/Transformation.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SupportedTransformsParam } from "../constants/supportedTransforms"; - -export type TransformationPosition = "path" | "query"; - -export type Transformation = Partial< - | { - [key in SupportedTransformsParam]: string | boolean | number; - } - | { [key: string]: string | boolean | number } ->; \ No newline at end of file diff --git a/libs/interfaces/UploadOptions.ts b/libs/interfaces/UploadOptions.ts deleted file mode 100644 index 47389b21..00000000 --- a/libs/interfaces/UploadOptions.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ReadStream } from "fs"; -import { Extension } from "./FileDetails"; - -interface TransformationObject { - type: "transformation"; - value: string; -} -interface GifToVideoOrThumbnailObject { - type: "gif-to-video" | "thumbnail"; - value?: string; -} - -interface AbsObject { - type: "abs"; - value: string; - protocol: "hls" | "dash"; -} - -type PostTransformation = TransformationObject | GifToVideoOrThumbnailObject | AbsObject; - -interface Transformation{ - pre?: string - post?: PostTransformation[] -} - -/** - * Options used when uploading a file - * - * @see {@link https://docs.imagekit.io/api-reference/upload-file-api/server-side-file-upload#request-structure-multipart-form-data} - */ -export interface UploadOptions { - /** - * This field accepts three kinds of values: - * - binary - You can send the content of the file as binary. This is used when a file is being uploaded from the browser. - * - base64 - Base64 encoded string of file content. - * - url - URL of the file from where to download the content before uploading. - * Downloading file from URL might take longer, so it is recommended that you pass the binary or base64 content of the file. - * Pass the full URL, for example - https://www.example.com/rest-of-the-image-path.jpg. - */ - file: string | Buffer | ReadStream; - /** - * The name with which the file has to be uploaded. - * The file name can contain: - * - Alphanumeric Characters: a-z , A-Z , 0-9 - * - Special Characters: . _ and - - * Any other character including space will be replaced by _ - */ - fileName: string; - /** - * Whether to use a unique filename for this file or not. - * - Accepts true or false. - * - If set true, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename. - * - If set false, then the image is uploaded with the provided filename parameter and any existing file with the same name is replaced. - * Default value - true - */ - useUniqueFileName?: boolean; - /** - * Set the tags while uploading the file. - * - Comma-separated value of tags in format tag1,tag2,tag3. For example - t-shirt,round-neck,men - * - The maximum length of all characters should not exceed 500. - * - % is not allowed. - * - If this field is not specified and the file is overwritten then the tags will be removed. - */ - tags?: string | string[]; - /** - * The folder path (e.g. /images/folder/) in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. - * The folder name can contain: - * - Alphanumeric Characters: a-z , A-Z , 0-9 - * - Special Characters: / _ and - - * - Using multiple / creates a nested folder. - * Default value - / - */ - folder?: string; - /** - * Whether to mark the file as private or not. This is only relevant for image type files. - * - Accepts true or false. - * - If set true, the file is marked as private which restricts access to the original image URL and unnamed image transformations without signed URLs. - * Without the signed URL, only named transformations work on private images - * Default value - false - */ - isPrivateFile?: boolean; - /** - * Define an important area in the image. This is only relevant for image type files. - * To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in format x,y,width,height. For example - 10,10,100,100 - * Can be used with fo-customtransformation. - * If this field is not specified and the file is overwritten, then customCoordinates will be removed. - */ - customCoordinates?: string; - /** - * Comma-separated values of the fields that you want ImageKit.io to return in response. - * - * For example, set the value of this field to tags,customCoordinates,isPrivateFile,metadata to get value of tags, customCoordinates, isPrivateFile , and metadata in the response. - */ - responseFields?: string | string[]; - /* - * Object with array of extensions to be processed on the image. - */ - extensions?: Extension; - /* - * Final status of pending extensions will be sent to this URL. - */ - webhookUrl?: string; - overwriteFile?: boolean; - overwriteAITags?: boolean; - overwriteTags?: boolean; - overwriteCustomMetadata?: boolean; - customMetadata?: { - [key: string]: string | number | boolean | Array; - }, - transformation?: Transformation - /** - * Optional `checks` parameters can be used to run server-side checks before files are uploaded to the Media Library. - */ - checks?: string - /** - * Optional. Determines whether the file should be uploaded as published. - * If set to false, the file will be marked as unpublished, restricting access to the file through the media library only. - * Files in draft or unpublished states can only be publicly accessed after they are published. - */ - isPublished?: boolean -} diff --git a/libs/interfaces/UploadResponse.ts b/libs/interfaces/UploadResponse.ts deleted file mode 100644 index 9ca44376..00000000 --- a/libs/interfaces/UploadResponse.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { AITagItem, CMValues, EmbeddedMetadataValues } from "./FileDetails"; -import { FileMetadataResponse } from "./FileMetadata"; -import { FileType } from "./FileType"; - -/** - * Response from uploading a file - * - * @see {@link https://docs.imagekit.io/api-reference/upload-file-api/server-side-file-upload#response-code-and-structure-json} - */ -export interface UploadResponse { - /** - * Unique fileId. Store this fileld in your database, as this will be used to perform update action on this file. - */ - fileId: string; - /** - * The name of the uploaded file. - */ - name: string; - /** - * The URL of the file. - */ - url: string; - /** - * In case of an image, a small thumbnail URL. - */ - thumbnailUrl: string; - /** - * Height of the uploaded image file. Only applicable when file type is image. - */ - height: number; - /** - * Width of the uploaded image file. Only applicable when file type is image. - */ - width: number; - /** - * Size of the uploaded file in bytes. - */ - size: number; - /** - * Type of file. It can either be image or non-image. - */ - fileType: FileType; - /** - * The path of the file uploaded. It includes any folder that you specified while uploading. - */ - filePath: string; - /** - * Array of tags associated with the image. - */ - tags?: string[]; - /** - * Is the file marked as private. It can be either true or false. - */ - isPrivateFile: boolean; - /** - * Value of custom coordinates associated with the image in format x,y,width,height. - */ - customCoordinates: string | null; - /** - * The metadata of the upload file. Use responseFields property in request to get the metadata returned in response of upload API. - */ - metadata?: FileMetadataResponse; - /* - * AITags field is populated only because the google-auto-tagging extension was executed synchronously and it received a successresponse. - */ - AITags?: AITagItem[]; - /* - * Field object which will contain the status of each extension at the time of completion of the update/upload request. - */ - extensionStatus?: { [key: string]: string } - /* - * Consolidated embedded metadata associated with the file. It includes exif, iptc, and xmp data. - */ - embeddedMetadata?: EmbeddedMetadataValues | null; - /* - * A key-value data associated with the asset. Before setting any custom metadata on an asset, you have to create the field using custom metadata fields API. - */ - customMetadata?: CMValues; - /** - * Is the file published or in draft state. It can be either true or false. - */ - isPublished?: boolean -} diff --git a/libs/interfaces/UrlOptions.ts b/libs/interfaces/UrlOptions.ts deleted file mode 100644 index d3dc6ac7..00000000 --- a/libs/interfaces/UrlOptions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { TransformationPosition } from "."; -import { Transformation } from "./Transformation"; - -export interface UrlOptionsBase { - /** - * An array of objects specifying the transformations to be applied in the URL. - * The transformation name and the value should be specified as a key-value pair in each object. - * @see {@link https://docs.imagekit.io/features/image-transformations/chained-transformations} - */ - transformation?: Array; - /** - * Default value is path that places the transformation string as a path parameter in the URL. - * Can also be specified as query which adds the transformation string as the query parameter tr in the URL. - * If you use src parameter to create the URL, then the transformation string is always added as a query parameter. - */ - transformationPosition?: TransformationPosition; - /** - * These are the other query parameters that you want to add to the final URL. - * These can be any query parameters and not necessarily related to ImageKit. - * Especially useful, if you want to add some versioning parameter to your URLs. - */ - queryParameters?: { [key: string]: string }; - /** - * The base URL to be appended before the path of the image. - * If not specified, the URL Endpoint specified at the time of SDK initialization is used. - */ - urlEndpoint?: string; - /** - * Default is false. If set to true, the SDK generates a signed image URL adding the image signature to the image URL. - * If you are creating URL using src parameter instead of path then do correct urlEndpoint for this to work. - * Otherwise returned URL will have wrong signature. - */ - signed?: boolean; - /** - * Meant to be used along with the signed parameter to specify the time in seconds from now when the URL should expire. - * If specified, the URL contains the expiry timestamp in the URL and the image signature is modified accordingly. - */ - expireSeconds?: number; -} - -export interface UrlOptionsSrc extends UrlOptionsBase { - /** - * Conditional. This is the complete URL of an image already mapped to ImageKit. - * For example, https://ik.imagekit.io/your_imagekit_id/endpoint/path/to/image.jpg. - * Either the path or src parameter need to be specified for URL generation. - */ - src: string; - path?: never; -} - -export interface UrlOptionsPath extends UrlOptionsBase { - /** - * Conditional. This is the path at which the image exists. - * For example, /path/to/image.jpg. Either the path or src parameter need to be specified for URL generation. - */ - path: string; - src?: never; -} - -/** - * Options for generating an URL - * - * @see {@link https://github.com/imagekit-developer/imagekit-nodejs#url-generation} - */ -export type UrlOptions = UrlOptionsSrc | UrlOptionsPath; diff --git a/libs/interfaces/index.ts b/libs/interfaces/index.ts deleted file mode 100644 index 538a897d..00000000 --- a/libs/interfaces/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ImageKitOptions } from "./ImageKitOptions"; -import { Transformation, TransformationPosition } from "./Transformation"; -import { UploadOptions } from "./UploadOptions"; -import { UploadResponse } from "./UploadResponse"; -import { FileType } from "./FileType"; -import { UrlOptions } from "./UrlOptions"; -import { ListFileOptions, ListFileResponse } from "./ListFile"; -import { CopyFileOptions } from "./CopyFile"; -import { MoveFileOptions } from "./MoveFile"; -import { CreateFolderOptions } from "./CreateFolder"; -import { FileDetailsOptions, FileVersionDetailsOptions, FileObject, FolderObject } from "./FileDetails"; -import { FileMetadataResponse } from "./FileMetadata"; -import { PurgeCacheResponse, PurgeCacheStatusResponse } from "./PurgeCache"; -import { BulkDeleteFilesResponse, BulkDeleteFilesError } from "./BulkDeleteFiles"; -import { CopyFolderOptions, CopyFolderResponse, CopyFolderError } from "./CopyFolder"; -import { MoveFolderOptions, MoveFolderResponse, MoveFolderError } from "./MoveFolder"; -import { DeleteFileVersionOptions, RestoreFileVersionOptions } from "./FileVersion" -import { CreateCustomMetadataFieldOptions, CustomMetadataField, UpdateCustomMetadataFieldOptions, GetCustomMetadataFieldsOptions } from "./CustomMetatadaField" -import { RenameFileOptions, RenameFileResponse } from "./Rename" -import { - WebhookEvent, - WebhookEventVideoTransformationAccepted, - WebhookEventVideoTransformationReady, - WebhookEventVideoTransformationError, - WebhookEventUploadPreTransformationSuccess, - WebhookEventUploadPreTransformationError, - WebhookEventUploadPostTransformationSuccess, - WebhookEventUploadPostTransformationError -} from "./webhookEvent"; - -type FinalUrlOptions = ImageKitOptions & UrlOptions; // actual options used to construct url - -export type { - ImageKitOptions, - Transformation, - TransformationPosition, - UploadOptions, - UploadResponse, - FileType, - UrlOptions, - FinalUrlOptions, - ListFileOptions, - ListFileResponse, - FileDetailsOptions, - FileVersionDetailsOptions, - FileObject, - FolderObject, - FileMetadataResponse, - PurgeCacheResponse, - PurgeCacheStatusResponse, - BulkDeleteFilesResponse, - BulkDeleteFilesError, - CopyFolderResponse, - CopyFolderError, - MoveFolderResponse, - MoveFolderError, - CopyFileOptions, - MoveFileOptions, - CreateFolderOptions, - CopyFolderOptions, - MoveFolderOptions, - DeleteFileVersionOptions, - RestoreFileVersionOptions, - CreateCustomMetadataFieldOptions, - GetCustomMetadataFieldsOptions, - CustomMetadataField, - UpdateCustomMetadataFieldOptions, - RenameFileOptions, - RenameFileResponse, - WebhookEvent, - WebhookEventVideoTransformationAccepted, - WebhookEventVideoTransformationReady, - WebhookEventVideoTransformationError, - WebhookEventUploadPostTransformationSuccess, - WebhookEventUploadPostTransformationError, - WebhookEventUploadPreTransformationSuccess, - WebhookEventUploadPreTransformationError -}; -export type { IKCallback } from "./IKCallback"; diff --git a/libs/interfaces/webhookEvent.ts b/libs/interfaces/webhookEvent.ts deleted file mode 100644 index 194afdcd..00000000 --- a/libs/interfaces/webhookEvent.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { UploadResponse } from "./UploadResponse"; - -type Asset = { - url: string; -}; - -type TransformationOptions = { - video_codec: string; - audio_codec: string; - auto_rotate: boolean; - quality: number; - format: string; -}; - -interface WebhookEventBase { - type: string; - id: string; - created_at: string; // Date -} - -/** WebhookEvent for "video.transformation.*" type */ -interface WebhookEventVideoTransformationBase extends WebhookEventBase { - request: { - x_request_id: string; - url: string; - user_agent: string; - }; -} - -export interface WebhookEventVideoTransformationAccepted extends WebhookEventVideoTransformationBase { - type: "video.transformation.accepted"; - data: { - asset: Asset; - transformation: { - type: string; - options: TransformationOptions; - }; - }; -} - -export interface WebhookEventVideoTransformationReady extends WebhookEventVideoTransformationBase { - type: "video.transformation.ready"; - timings: { - donwload_duration: number; - encoding_duration: number; - }; - data: { - asset: Asset; - transformation: { - type: string; - options: TransformationOptions; - output: { - url: string; - video_metadata: { - duration: number; - width: number; - height: number; - bitrate: number; - }; - }; - }; - }; -} - -export interface WebhookEventVideoTransformationError extends WebhookEventVideoTransformationBase { - type: "video.transformation.error"; - data: { - asset: Asset; - transformation: { - type: string; - options: TransformationOptions; - error: { - reason: string; - }; - }; - }; -} - -type TransformationType = "transformation" | "abs" | "gif-to-video" | "thumbnail"; - -interface PreTransformationBase { - id: string; - created_at: string; - request: { - x_request_id: string; - transformation: string; - }; -} - -interface PostTransformationBase { - id: string; - created_at: string; - request: { - x_request_id: string; - transformation: { - type: TransformationType; - value?: string; - protocol?: 'hls' | 'dash'; - }; - }; -} - -export interface WebhookEventUploadPreTransformationSuccess extends PreTransformationBase { - type: "upload.pre-transform.success"; - data: UploadResponse; -} - -export interface WebhookEventUploadPreTransformationError extends PostTransformationBase { - type: "upload.pre-transform.error"; - data: { - name: string; - path: string; - transformation: { - error: { - reason: string; - }; - }; - }; -} - -export interface WebhookEventUploadPostTransformationSuccess extends PostTransformationBase { - type: "upload.post-transform.success"; - data: { - fileId: string; - url: string; - name: string; - }; -} - -export interface WebhookEventUploadPostTransformationError extends PostTransformationBase { - type: "upload.post-transform.error"; - data: { - fileId: string; - url: string; - name: string; - path: string; - transformation: { - error: { - reason: string; - }; - }; - }; -} - -export type WebhookEvent = - | WebhookEventVideoTransformationAccepted - | WebhookEventVideoTransformationReady - | WebhookEventVideoTransformationError - | WebhookEventUploadPreTransformationSuccess - | WebhookEventUploadPreTransformationError - | WebhookEventUploadPostTransformationSuccess - | WebhookEventUploadPostTransformationError - | Object; diff --git a/libs/manage/cache.ts b/libs/manage/cache.ts deleted file mode 100644 index a3bb4ef7..00000000 --- a/libs/manage/cache.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - Constants -*/ -import errorMessages from "../constants/errorMessages"; - -/* - Utils -*/ -import respond from "../../utils/respond"; -import request from "../../utils/request"; - -/* - Interfaces -*/ -import { IKCallback } from "../interfaces/IKCallback"; -import { ImageKitOptions, PurgeCacheResponse, PurgeCacheStatusResponse } from "../interfaces"; - -const purgeCache = function (url: string, defaultOptions: ImageKitOptions, callback?: IKCallback) { - if (!url && !url.length) { - respond(true, errorMessages.CACHE_PURGE_URL_MISSING, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/purge", - method: "POST", - json: { - url: url, - }, - }; - - request(requestOptions, defaultOptions, callback); -}; - -const getPurgeCacheStatus = function ( - requestId: string, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!requestId && !requestId.length) { - respond(true, errorMessages.CACHE_PURGE_STATUS_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/purge/" + requestId, - method: "GET", - }; - - request(requestOptions, defaultOptions, callback); -}; - -export default { purgeCache, getPurgeCacheStatus }; diff --git a/libs/manage/custom-metadata-field.ts b/libs/manage/custom-metadata-field.ts deleted file mode 100644 index 752ab080..00000000 --- a/libs/manage/custom-metadata-field.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - Constants -*/ -import errorMessages from "../constants/errorMessages"; - -/* - Utils -*/ -import respond from "../../utils/respond"; -import request from "../../utils/request"; - -/* - Interfaces -*/ -import { IKCallback } from "../interfaces/IKCallback"; -import { - ImageKitOptions, - CreateCustomMetadataFieldOptions, - CustomMetadataField, - UpdateCustomMetadataFieldOptions, - GetCustomMetadataFieldsOptions, -} from "../interfaces"; - -const create = function (createCustomMetadataFieldOptions: CreateCustomMetadataFieldOptions, defaultOptions: ImageKitOptions, callback?: IKCallback) { - const { name, label, schema } = createCustomMetadataFieldOptions; - if (!name || !name.length) { - respond(true, errorMessages.CMF_NAME_MISSING, callback); - return; - } - - if (!label || !label.length) { - respond(true, errorMessages.CMF_LABEL_MISSING, callback); - return; - } - - if (!schema) { - respond(true, errorMessages.CMF_SCHEMA_MISSING, callback); - return; - } - - if (!schema.type) { - respond(true, errorMessages.CMF_SCHEMA_INVALID, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/customMetadataFields", - method: "POST", - json: { - name, - label, - schema - }, - }; - - request(requestOptions, defaultOptions, callback); -}; - -const list = function ( - getCustomMetadataFieldsOptions: GetCustomMetadataFieldsOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { includeDeleted = false } = getCustomMetadataFieldsOptions || {}; - var requestOptions = { - url: "https://api.imagekit.io/v1/customMetadataFields", - method: "GET", - qs: { includeDeleted } - }; - - request(requestOptions, defaultOptions, callback); -}; - -const update = function (fieldId: string, updateCustomMetadataFieldOptions: UpdateCustomMetadataFieldOptions, defaultOptions: ImageKitOptions, callback?: IKCallback) { - if (!fieldId || typeof fieldId !== "string" || !fieldId.length) { - respond(true, errorMessages.CMF_FIELD_ID_MISSING, callback); - return; - } - - const { label, schema } = updateCustomMetadataFieldOptions; - if (!label && !schema) { - respond(true, errorMessages.CMF_LABEL_SCHEMA_MISSING, callback); - return; - } - - var requestBody: UpdateCustomMetadataFieldOptions = {}; - if (label) requestBody.label = label; - if (schema) requestBody.schema = schema; - - var requestOptions = { - url: `https://api.imagekit.io/v1/customMetadataFields/${fieldId}`, - method: "PATCH", - json: requestBody - }; - - request(requestOptions, defaultOptions, callback); -}; - -const deleteField = function ( - fieldId: string, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!fieldId || typeof fieldId !== "string" || !fieldId.length) { - respond(true, errorMessages.CMF_FIELD_ID_MISSING, callback); - return; - } - var requestOptions = { - url: `https://api.imagekit.io/v1/customMetadataFields/${fieldId}`, - method: "DELETE", - }; - - request(requestOptions, defaultOptions, callback); -}; - -export default { create, list, update, deleteField }; diff --git a/libs/manage/file.ts b/libs/manage/file.ts deleted file mode 100644 index 5ee18da7..00000000 --- a/libs/manage/file.ts +++ /dev/null @@ -1,699 +0,0 @@ -import { isObject } from 'lodash'; - -/* - Constants -*/ -import errorMessages from "../constants/errorMessages"; - -/* - Utils -*/ -import respond from "../../utils/respond"; -import request from "../../utils/request"; - -/* - Interfaces -*/ -import { IKCallback } from "../interfaces/IKCallback"; -import { - ImageKitOptions, - ListFileOptions, - ListFileResponse, - FileDetailsOptions, - FileVersionDetailsOptions, - FileObject, - FileMetadataResponse, - BulkDeleteFilesResponse, - BulkDeleteFilesError, - CopyFileOptions, - CopyFolderResponse, - MoveFileOptions, - CreateFolderOptions, - CopyFolderOptions, - MoveFolderOptions, - DeleteFileVersionOptions, - RestoreFileVersionOptions, - RenameFileOptions, - RenameFileResponse, -} from "../interfaces"; -import ImageKit from "../.."; - -/* - Delete a file -*/ -const deleteFile = function (fileId: string, defaultOptions: ImageKitOptions, callback?: IKCallback) { - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/" + fileId, - method: "DELETE" - }; - - request(requestOptions, defaultOptions, callback); -}; - - -/* - Delete a file version -*/ -const deleteFileVersion = function (deleteFileVersionOptions: DeleteFileVersionOptions, defaultOptions: ImageKitOptions, callback?: IKCallback) { - const { fileId, versionId } = deleteFileVersionOptions || {}; - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - if (!versionId) { - respond(true, errorMessages.FILE_VERSION_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: `https://api.imagekit.io/v1/files/${fileId}/versions/${versionId}`, - method: "DELETE" - }; - - request(requestOptions, defaultOptions, callback); -}; - - -/* - Restore a file version as the current version -*/ -const restoreFileVersion = function ( - restoreFileVersionOptions: RestoreFileVersionOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback) { - const { fileId, versionId } = restoreFileVersionOptions || {}; - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - if (!versionId) { - respond(true, errorMessages.FILE_VERSION_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: `https://api.imagekit.io/v1/files/${fileId}/versions/${versionId}/restore`, - method: "PUT" - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Get Metadata of a file -*/ -const getMetadata = function ( - fileIdOrURL: string, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!fileIdOrURL || fileIdOrURL.trim() == "") { - respond(true, errorMessages.FILE_ID_OR_URL_MISSING, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/" + fileIdOrURL + "/metadata", - method: "GET" - }; - - // In case of URL change the endopint - if (fileIdOrURL.indexOf("http") === 0) { - requestOptions = { - url: `https://api.imagekit.io/v1/metadata?url=${fileIdOrURL}`, - method: "GET" - }; - } - - request(requestOptions, defaultOptions, callback); -}; - -/* - Get Details of a file -*/ -const getDetails = function ( - fileId: string, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/" + fileId + "/details", - method: "GET" - }; - - request(requestOptions, defaultOptions, callback); -}; - - -/* - Get Details of a file version -*/ -const getFileVersionDetails = function ( - fileDetailsOptions: FileVersionDetailsOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { fileId, versionId } = fileDetailsOptions || {}; - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - if (!versionId) { - respond(true, errorMessages.FILE_VERSION_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: `https://api.imagekit.io/v1/files/${fileId}/versions/${versionId}`, - method: "GET" - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Update file details -*/ -const updateDetails = function ( - fileId: string, - updateData: FileDetailsOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - if (!isObject(updateData)) { - respond(true, errorMessages.UPDATE_DATA_MISSING, callback); - return; - } - - var data = {}; - data = { - tags: updateData.tags, - customCoordinates: updateData.customCoordinates, - extensions: updateData.extensions, - webhookUrl: updateData.webhookUrl, - customMetadata: updateData.customMetadata, - }; - - if (updateData.publish) - data = { - ...data, - publish: updateData.publish, - }; - - var requestOptions = { - url: "https://api.imagekit.io/v1/files/" + fileId + "/details", - method: "PATCH", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - List files -*/ -const listFiles = function ( - listOptions: ListFileOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (listOptions && !isObject(listOptions)) { - respond(true, errorMessages.INVALID_LIST_OPTIONS, callback); - return; - } - - if (listOptions && listOptions.tags && Array.isArray(listOptions.tags) && listOptions.tags.length) { - listOptions.tags = listOptions.tags.join(","); - } - - var requestOptions = { - url: `https://api.imagekit.io/v1/files/`, - method: "GET", - qs: listOptions || {} - }; - - request(requestOptions, defaultOptions, callback); -}; - - -/* - Get all versions of an asset -*/ -const getFilesVersions = function ( - fileId: string, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if (!fileId) { - respond(true, errorMessages.FILE_ID_MISSING, callback); - return; - } - - var requestOptions = { - url: `https://api.imagekit.io/v1/files/${fileId}/versions`, - method: "GET" - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Bulk Delete By FileIds -*/ -const bulkDeleteFiles = function ( - fileIdArray: string[], - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if ( - !Array.isArray(fileIdArray) || - fileIdArray.length === 0 || - fileIdArray.filter((fileId) => typeof fileId !== "string").length > 0 - ) { - respond(true, errorMessages.INVALID_FILEIDS_VALUE, callback); - return; - } - - const data = { - fileIds: fileIdArray, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/batch/deleteByFileIds", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Add tags in bulk -*/ -const bulkAddTags = function ( - fileIdArray: string[], - tags: string[], - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if ( - !Array.isArray(fileIdArray) || - fileIdArray.length === 0 || - fileIdArray.filter((fileId) => typeof fileId !== "string").length > 0 - ) { - respond(true, errorMessages.INVALID_FILEIDS_VALUE, callback); - return; - } - - if (!Array.isArray(tags) || tags.length === 0 || tags.filter((tag) => typeof tag !== "string").length > 0) { - respond(true, errorMessages.BULK_ADD_TAGS_INVALID, callback); - return; - } - - const data = { - fileIds: fileIdArray, - tags: tags, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/addTags", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Remove tags in bulk -*/ -const bulkRemoveTags = function ( - fileIdArray: string[], - tags: string[], - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if ( - !Array.isArray(fileIdArray) || - fileIdArray.length === 0 || - fileIdArray.filter((fileId) => typeof fileId !== "string").length > 0 - ) { - respond(true, errorMessages.INVALID_FILEIDS_VALUE, callback); - return; - } - - if (!Array.isArray(tags) || tags.length === 0 || tags.filter((tag) => typeof tag !== "string").length > 0) { - respond(true, errorMessages.BULK_ADD_TAGS_INVALID, callback); - return; - } - - const data = { - fileIds: fileIdArray, - tags: tags, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/removeTags", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - - -/* - Remove AI tags in bulk -*/ -const bulkRemoveAITags = function ( - fileIdArray: string[], - tags: string[], - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - if ( - !Array.isArray(fileIdArray) || - fileIdArray.length === 0 || - fileIdArray.filter((fileId) => typeof fileId !== "string").length > 0 - ) { - respond(true, errorMessages.INVALID_FILEIDS_VALUE, callback); - return; - } - - if (!Array.isArray(tags) || tags.length === 0 || tags.filter((tag) => typeof tag !== "string").length > 0) { - respond(true, errorMessages.BULK_ADD_TAGS_INVALID, callback); - return; - } - - const data = { - fileIds: fileIdArray, - AITags: tags, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/removeAITags", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Copy file -*/ -const copyFile = function ( - copyFileOptions: CopyFileOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { sourceFilePath, destinationPath, includeFileVersions = false } = copyFileOptions; - - if (typeof sourceFilePath !== "string" || sourceFilePath.length === 0) { - respond(true, errorMessages.INVALID_SOURCE_FILE_PATH, callback); - return; - } - - if (typeof destinationPath !== "string" || destinationPath.length === 0) { - respond(true, errorMessages.INVALID_DESTINATION_FOLDER_PATH, callback); - return; - } - - if (typeof includeFileVersions !== "boolean") { - respond(true, errorMessages.INVALID_INCLUDE_VERSION, callback); - return; - } - - const data = { - sourceFilePath, - destinationPath, - includeFileVersions - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/copy", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Move file -*/ -const moveFile = function ( - moveFileOptions: MoveFileOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { sourceFilePath, destinationPath } = moveFileOptions; - if (typeof sourceFilePath !== "string" || sourceFilePath.length === 0) { - respond(true, errorMessages.INVALID_SOURCE_FILE_PATH, callback); - return; - } - - if (typeof destinationPath !== "string" || destinationPath.length === 0) { - respond(true, errorMessages.INVALID_DESTINATION_FOLDER_PATH, callback); - return; - } - - const data = { - sourceFilePath, - destinationPath - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/move", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Rename file -*/ -const renameFile = function ( - renameFileOptions: RenameFileOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { filePath, newFileName, purgeCache = false } = renameFileOptions; - if (typeof filePath !== "string" || filePath.length === 0) { - respond(true, errorMessages.INVALID_FILE_PATH, callback); - return; - } - - if (typeof newFileName !== "string" || newFileName.length === 0) { - respond(true, errorMessages.INVALID_NEW_FILE_NAME, callback); - return; - } - - if (typeof purgeCache !== "boolean") { - respond(true, errorMessages.INVALID_PURGE_CACHE, callback); - return; - } - - const data = { - filePath, - newFileName, - purgeCache - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/files/rename", - method: "PUT", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Copy Folder -*/ -const copyFolder = function ( - copyFolderOptions: CopyFolderOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { sourceFolderPath, destinationPath, includeFileVersions = false } = copyFolderOptions; - if (typeof sourceFolderPath !== "string" || sourceFolderPath.length === 0) { - respond(true, errorMessages.INVALID_SOURCE_FOLDER_PATH, callback); - return; - } - - if (typeof destinationPath !== "string" || destinationPath.length === 0) { - respond(true, errorMessages.INVALID_DESTINATION_FOLDER_PATH, callback); - return; - } - - if (typeof includeFileVersions !== "boolean") { - respond(true, errorMessages.INVALID_INCLUDE_VERSION, callback); - return; - } - - const data = { - sourceFolderPath, - destinationPath, - includeFileVersions - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/bulkJobs/copyFolder", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Move Folder -*/ -const moveFolder = function ( - moveFolderOptions: MoveFolderOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { sourceFolderPath, destinationPath } = moveFolderOptions; - - if (typeof sourceFolderPath !== "string" || sourceFolderPath.length === 0) { - respond(true, errorMessages.INVALID_SOURCE_FOLDER_PATH, callback); - return; - } - - if (typeof destinationPath !== "string" || destinationPath.length === 0) { - respond(true, errorMessages.INVALID_DESTINATION_FOLDER_PATH, callback); - return; - } - - const data = { - sourceFolderPath, - destinationPath, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/bulkJobs/moveFolder", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Create folder -*/ -const createFolder = function ( - createFolderOptions: CreateFolderOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - const { folderName, parentFolderPath } = createFolderOptions; - if (typeof folderName !== "string" || folderName.length === 0) { - respond(true, errorMessages.INVALID_FOLDER_NAME, callback); - return; - } - - if (typeof parentFolderPath !== "string" || parentFolderPath.length === 0) { - respond(true, errorMessages.INVALID_PARENT_FOLDER_PATH, callback); - return; - } - - const data = { - folderName: folderName, - parentFolderPath: parentFolderPath, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/folder", - method: "POST", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Delete folder -*/ -const deleteFolder = function (folderPath: string, defaultOptions: ImageKitOptions, callback?: IKCallback) { - if (typeof folderPath !== "string" || folderPath.length === 0) { - respond(true, errorMessages.INVALID_FOLDER_PATH, callback); - return; - } - - const data = { - folderPath: folderPath, - }; - - const requestOptions = { - url: "https://api.imagekit.io/v1/folder", - method: "DELETE", - json: data, - }; - - request(requestOptions, defaultOptions, callback); -}; - -/* - Bulk job status -*/ -const getBulkJobStatus = function (jobId: string, defaultOptions: ImageKitOptions, callback?: IKCallback) { - if (!jobId) { - respond(true, errorMessages.JOB_ID_MISSING, callback); - return; - } - - const requestOptions = { - url: "https://api.imagekit.io/v1/bulkJobs/" + jobId, - method: "GET" - }; - - request(requestOptions, defaultOptions, callback); -}; - -export default { - deleteFile, - getMetadata, - getDetails, - getFileVersionDetails, - updateDetails, - listFiles, - getFilesVersions, - bulkDeleteFiles, - deleteFileVersion, - restoreFileVersion, - bulkAddTags, - bulkRemoveTags, - bulkRemoveAITags, - copyFile, - moveFile, - renameFile, - copyFolder, - moveFolder, - createFolder, - deleteFolder, - getBulkJobStatus, -}; diff --git a/libs/manage/index.ts b/libs/manage/index.ts deleted file mode 100644 index 72f4ac34..00000000 --- a/libs/manage/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import cache from "./cache"; -import file from "./file"; -import customMetadataField from "./custom-metadata-field"; - -export default { - listFiles: file.listFiles, - getFileDetails: file.getDetails, - getFileVersions: file.getFilesVersions, - getFileVersionDetails: file.getFileVersionDetails, - updateFileDetails: file.updateDetails, - getFileMetadata: file.getMetadata, - deleteFile: file.deleteFile, - bulkDeleteFiles: file.bulkDeleteFiles, - deleteFileVersion: file.deleteFileVersion, - restoreFileVersion: file.restoreFileVersion, - bulkAddTags: file.bulkAddTags, - bulkRemoveTags: file.bulkRemoveTags, - bulkRemoveAITags: file.bulkRemoveAITags, - copyFile: file.copyFile, - moveFile: file.moveFile, - renameFile: file.renameFile, - copyFolder: file.copyFolder, - moveFolder: file.moveFolder, - createFolder: file.createFolder, - deleteFolder: file.deleteFolder, - getBulkJobStatus: file.getBulkJobStatus, - purgeCache: cache.purgeCache, - getPurgeCacheStatus: cache.getPurgeCacheStatus, - createCustomMetadataField: customMetadataField.create, - getCustomMetadataFields: customMetadataField.list, - updateCustomMetadataField: customMetadataField.update, - deleteCustomMetadataField: customMetadataField.deleteField, -}; diff --git a/libs/signature/index.ts b/libs/signature/index.ts deleted file mode 100644 index 8d17acec..00000000 --- a/libs/signature/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - Helper Modules -*/ -import { v4 as uuid } from "uuid"; -import crypto from "crypto"; -import { ImageKitOptions } from "../interfaces"; -var DEFAULT_TIME_DIFF = 60 * 30; - -const getAuthenticationParameters = function (token?: string, expire?: number, defaultOptions?: ImageKitOptions) { - var defaultExpire = parseInt(String(new Date().getTime() / 1000), 10) + DEFAULT_TIME_DIFF; - var authParameters = { - token: token || "", - expire: expire || 0, - signature: "", - }; - - if (!defaultOptions || !defaultOptions.privateKey) return authParameters; - - token = token || uuid(); - expire = expire || defaultExpire; - var signature = crypto - .createHmac("sha1", defaultOptions.privateKey) - .update(token + expire) - .digest("hex"); - - authParameters.token = token; - authParameters.expire = expire; - authParameters.signature = signature; - - return authParameters; -}; - -export default { getAuthenticationParameters }; diff --git a/libs/upload/index.ts b/libs/upload/index.ts deleted file mode 100644 index 7d1a8577..00000000 --- a/libs/upload/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -import _ from "lodash"; -import errorMessages from "../constants/errorMessages"; -import respond from "../../utils/respond"; -import request from "../../utils/request"; -import { IKCallback } from "../interfaces/IKCallback"; -import { ImageKitOptions, UploadOptions, UploadResponse } from "../interfaces"; -import FormData from "form-data"; - -type Modify = Omit & R; -type FormDataOptions = Modify< - UploadOptions, - { - file: string | Buffer | object; - useUniqueFileName: string; - isPrivateFile: string; - extensions?: string; - webhookUrl?: string; - overwriteFile?: string; - overwriteAITags?: string; - overwriteTags?: string; - overwriteCustomMetadata?: string; - customMetadata?: string; - } ->; - -export default function ( - uploadOptions: UploadOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -): void | Promise { - if (!_.isObject(uploadOptions)) { - respond(true, errorMessages.MISSING_UPLOAD_DATA, callback); - return; - } - - if (!uploadOptions.file) { - respond(true, errorMessages.MISSING_UPLOAD_FILE_PARAMETER, callback); - return; - } - - if (!uploadOptions.fileName) { - respond(true, errorMessages.MISSING_UPLOAD_FILENAME_PARAMETER, callback); - return; - } - - if (uploadOptions.transformation) { - if (!(Object.keys(uploadOptions.transformation).includes("pre") || Object.keys(uploadOptions.transformation).includes("post"))) { - respond(true, errorMessages.INVALID_TRANSFORMATION, callback); - return; - } - if (Object.keys(uploadOptions.transformation).includes("pre") && !uploadOptions.transformation.pre) { - respond(true, errorMessages.INVALID_PRE_TRANSFORMATION, callback); - return; - } - if (Object.keys(uploadOptions.transformation).includes("post")) { - if (Array.isArray(uploadOptions.transformation.post)) { - for (let transformation of uploadOptions.transformation.post) { - if (transformation.type === "abs" && !(transformation.protocol || transformation.value)) { - respond(true, errorMessages.INVALID_POST_TRANSFORMATION, callback); - return; - } else if (transformation.type === "transformation" && !transformation.value) { - respond(true, errorMessages.INVALID_POST_TRANSFORMATION, callback); - return; - } - } - } else { - respond(true, errorMessages.INVALID_POST_TRANSFORMATION, callback); - return; - } - } - } - - var formData = {} as FormDataOptions; - - const form = new FormData(); - - let key: keyof typeof uploadOptions; - for (key in uploadOptions) { - if (key) { - if (key == "file" && typeof uploadOptions.file != "string") { - // form.append('file', uploadOptions.file); - form.append('file', uploadOptions.file, String(uploadOptions.fileName)); - } else if (key == "tags" && Array.isArray(uploadOptions.tags)) { - form.append('tags', uploadOptions.tags.join(",")); - } else if (key == "responseFields" && Array.isArray(uploadOptions.responseFields)) { - form.append('responseFields', uploadOptions.responseFields.join(",")); - } else if (key == "extensions" && Array.isArray(uploadOptions.extensions)) { - form.append('extensions', JSON.stringify(uploadOptions.extensions)); - } else if (key === "customMetadata" && typeof uploadOptions.customMetadata === "object" && - !Array.isArray(uploadOptions.customMetadata) && uploadOptions.customMetadata !== null) { - form.append('customMetadata', JSON.stringify(uploadOptions.customMetadata)); - } else if (key === "transformation" && typeof uploadOptions.transformation === "object" && - uploadOptions.transformation !== null) { - form.append(key, JSON.stringify(uploadOptions.transformation)); - } else if (key === "checks" && uploadOptions.checks) { - form.append(key, uploadOptions.checks); - } else { - form.append(key, String(uploadOptions[key])); - } - } - } - - var requestOptions = { - url: defaultOptions.uploadEndpoint || "https://upload.imagekit.io/api/v1/files/upload", - method: "POST", - formData: form - }; - - request(requestOptions, defaultOptions, callback); -} diff --git a/libs/url/builder.ts b/libs/url/builder.ts deleted file mode 100644 index 87319978..00000000 --- a/libs/url/builder.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - Helper Modules -*/ -import { URLSearchParams, URL } from "url"; -import path from "path"; -import crypto from "crypto"; - -/* - Utils -*/ -import transformationUtils from "../../utils/transformation"; -import urlFormatter from "../../utils/urlFormatter"; - -/* - Interfaces -*/ -import { FinalUrlOptions, Transformation } from "../interfaces"; - -/* - Variables -*/ -const TRANSFORMATION_PARAMETER: string = "tr"; -const SIGNATURE_PARAMETER: string = "ik-s"; -const TIMESTAMP_PARAMETER: string = "ik-t"; -const DEFAULT_TIMESTAMP: string = "9999999999"; - -//used to check if special char is present in string (you'll need to encode it to utf-8 if it does) -const hasMoreThanAscii = (str: string) => { - return str.split('').some((char) => char.charCodeAt(0) > 127); -} - -const customEncodeURI = (str: string) => { - return str.includes("?") ? `${encodeURI(str.split("?")[0])}?${str.split("?")[1]}` : encodeURI(str); -}; - -export const encodeStringIfRequired = (str: string) => { - return hasMoreThanAscii(str) ? customEncodeURI(str) : str; -} - -const buildURL = function (opts: FinalUrlOptions): string { - var isSrcParameterUsedForURL: boolean = false; - - var urlObject: URL; - - if (opts.path) { - urlObject = new URL(opts.urlEndpoint) - } else if (opts.src) { - isSrcParameterUsedForURL = true; - urlObject = new URL(opts.src) - } else { - return ""; - } - - - var queryParameters = new URLSearchParams(urlObject.search || ""); - for (var i in opts.queryParameters) { - queryParameters.set(i, opts.queryParameters[i]); - } - - //Create Transformation String - var transformationString = constructTransformationString(opts.transformation); - if (transformationString) { - //force that if src parameter is being used for URL construction then the transformation - //string should be added only as a query parameter - if (transformationUtils.addAsQueryParameter(opts) || isSrcParameterUsedForURL) { - queryParameters.set(TRANSFORMATION_PARAMETER, transformationString); - urlObject.pathname= `${urlObject.pathname}${opts.path||''}`; - } else { - urlObject.pathname = path.posix.join( - urlObject.pathname, - [TRANSFORMATION_PARAMETER, transformationString].join(transformationUtils.getChainTransformDelimiter()), - opts.path || '', - ); - } - } - else{ - urlObject.pathname= `${urlObject.pathname}${opts.path||''}`; - } - - urlObject.host = urlFormatter.removeTrailingSlash(urlObject.host); - urlObject.pathname = urlFormatter.addLeadingSlash(urlObject.pathname); - urlObject.search = queryParameters.toString(); - - /* - Signature String and Timestamp - If the url is constructed using src parameter instead of path then we still replace the urlEndpoint we have - But the user is responsible for passing correct urlEndpoint value - - Signature generation logic, let's assume: - urlEndpoint value = https://ik.imagekit.io/your_imagekit_id - expiryTimestamp 9999999999 - 1. Let the final URL construct e.g. https://ik.imagekit.io/your_imagekit_id/tr:w-400:rotate-91/sample/testing-file.jpg?param1=123 - 2. Now remove urlEndpoint from it i.e tr:w-400:rotate-91/sample/testing-file.jpg?param1=123 - 3. Append expiryTimestamp to above string and calcualte signature of this string i.e "tr:w-400:rotate-91/sample/testing-file.jpg?param1=1239999999999" - */ - var expiryTimestamp; - if (opts.signed === true) { - if (opts.expireSeconds) { - expiryTimestamp = getSignatureTimestamp(opts.expireSeconds); - } else { - expiryTimestamp = DEFAULT_TIMESTAMP; - } - - var intermediateURL = urlObject.href; - - var urlSignature = getSignature({ - privateKey: opts.privateKey, - url: intermediateURL, - urlEndpoint: opts.urlEndpoint, - expiryTimestamp: expiryTimestamp, - }); - - if (expiryTimestamp && expiryTimestamp != DEFAULT_TIMESTAMP) { - queryParameters.set(TIMESTAMP_PARAMETER, expiryTimestamp); - } - queryParameters.set(SIGNATURE_PARAMETER, urlSignature); - urlObject.search = queryParameters.toString(); - } - return urlObject.href; -}; - -function constructTransformationString(inputTransformation: Array | undefined) { - - const transformation = inputTransformation as Array<{ [key: string]: string | boolean | number }> | undefined; - if (!Array.isArray(transformation)) { - return ""; - } - - var parsedTransforms = []; - for (var i = 0, l = transformation.length; i < l; i++) { - var parsedTransformStep = []; - for (var key in transformation[i]) { - if(transformation[i][key] === undefined || transformation[i][key] === null ) - continue; - let transformKey = transformationUtils.getTransformKey(key); - if (!transformKey) { - transformKey = key; - } - - if (transformation[i][key] === "-") { - parsedTransformStep.push(transformKey); - } else if (key === "raw") { - parsedTransformStep.push(transformation[i][key]); - } else { - var value = String(transformation[i][key]); - if (transformKey === "di") { - value = urlFormatter.removeTrailingSlash(urlFormatter.removeLeadingSlash(value)); - if (value) value = value.replace(/\//g, "@@"); - } - parsedTransformStep.push([transformKey, value].join(transformationUtils.getTransformKeyValueDelimiter())); - } - } - parsedTransforms.push(parsedTransformStep.join(transformationUtils.getTransformDelimiter())); - } - - return parsedTransforms.join(transformationUtils.getChainTransformDelimiter()); -} - -function getSignatureTimestamp(seconds: number): string { - if (!seconds) return DEFAULT_TIMESTAMP; - - var sec = parseInt(String(seconds), 10); - if (!sec) return DEFAULT_TIMESTAMP; - - var currentTimestamp = parseInt(String(new Date().getTime() / 1000), 10); - return String(currentTimestamp + sec); -} - -export function getSignature(opts: any) { - if (!opts.privateKey || !opts.url || !opts.urlEndpoint) return ""; - var stringToSign = opts.url.replace(urlFormatter.addTrailingSlash(opts.urlEndpoint), "") + opts.expiryTimestamp; - stringToSign = encodeStringIfRequired(stringToSign); - return crypto.createHmac("sha1", opts.privateKey).update(stringToSign).digest("hex"); -} - -export default { - buildURL, - getSignature, -}; diff --git a/libs/url/index.ts b/libs/url/index.ts deleted file mode 100644 index 35cd2d50..00000000 --- a/libs/url/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - Helper Modules -*/ -import _ from "lodash"; -/* - Interfaces -*/ -import { UrlOptions, ImageKitOptions, FinalUrlOptions } from "../interfaces"; -/* - URL builder -*/ -import builder from "./builder"; - -export default function (urlOpts: UrlOptions, defaultOptions: ImageKitOptions): string { - var opts: FinalUrlOptions = _.extend({}, defaultOptions, urlOpts); - - return builder.buildURL(opts); -} diff --git a/package.json b/package.json index 40f220ed..7eaf6316 100644 --- a/package.json +++ b/package.json @@ -1,73 +1,71 @@ { - "name": "imagekit", - "version": "6.0.0", - "description": "Offical NodeJS SDK for ImageKit.io integration", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "scripts": { - "compile": "rm -rf dist/ & tsc -p tsconfig.json", - "test": "export NODE_ENV=test; nyc ./node_modules/mocha/bin/mocha --exit -t 40000 tests/*.js;ex=$?;unset NODE_ENV; exit $ex;", - "test-e2e": "sh test-e2e.sh; exit $?;", - "report-coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", - "prepack": "npm run compile" - }, - "author": "ImageKit Developer ", - "homepage": "https://imagekit.io", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git" + "name": "@imagekit/nodejs", + "version": "0.0.1-alpha.0", + "description": "The official TypeScript library for the Image Kit API", + "author": "Image Kit ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": "github:stainless-sdks/imagekit-typescript", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "files": [ + "**/*" + ], + "private": false, + "publishConfig": { + "access": "public" }, - "bugs": { - "url": "https://github.com/imagekit-developer/imagekit-nodejs/issues" + "scripts": { + "test": "./scripts/test", + "build": "./scripts/build", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "./scripts/format", + "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "./scripts/lint", + "fix": "./scripts/format" }, - "keywords": [ - "imagekit", - "nodejs", - "javascript", - "sdk", - "js", - "sdk", - "image", - "optimization", - "image", - "transformation", - "image", - "resize" - ], "dependencies": { - "axios": "^1.6.5", - "form-data": "^4.0.0", - "hamming-distance": "^1.0.0", - "lodash": "^4.17.15", - "tslib": "^2.4.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=12.0.0" + "standardwebhooks": "^1.0.0" }, "devDependencies": { - "@babel/cli": "^7.14.5", - "@babel/core": "^7.14.6", - "@babel/node": "^7.14.5", - "@babel/preset-env": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "@babel/register": "^7.14.5", - "@types/chai": "^4.3.1", - "@types/lodash": "^4.14.170", - "@types/mocha": "^9.1.1", - "@types/node": "^15.12.2", - "@types/request": "^2.48.5", - "@types/sinon": "^10.0.12", - "@types/uuid": "^8.3.4", - "babel-plugin-replace-ts-export-assignment": "^0.0.2", - "chai": "^4.2.0", - "codecov": "^3.8.0", - "concurrently": "6.5.1", - "mocha": "^8.1.1", - "nock": "^13.2.7", - "nyc": "^15.1.0", - "sinon": "^9.2.0", - "typescript": "^4.3.2" + "@arethetypeswrong/cli": "^0.17.0", + "@swc/core": "^1.3.102", + "@swc/jest": "^0.2.29", + "@types/jest": "^29.4.0", + "@types/node": "^20.17.6", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^9.20.1", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unused-imports": "^4.1.4", + "iconv-lite": "^0.6.3", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "publint": "^0.2.12", + "ts-jest": "^29.1.0", + "ts-node": "^10.5.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsconfig-paths": "^4.0.0", + "tslib": "^2.8.1", + "typescript": "5.8.3", + "typescript-eslint": "8.31.1" + }, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./*.mjs": { + "default": "./dist/*.mjs" + }, + "./*.js": { + "default": "./dist/*.js" + }, + "./*": { + "import": "./dist/*.mjs", + "require": "./dist/*.js" + } } } diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md new file mode 100644 index 00000000..a6726151 --- /dev/null +++ b/packages/mcp-server/README.md @@ -0,0 +1,367 @@ +# Image Kit TypeScript MCP Server + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + +### Building + +Because it's not published yet, clone the repo and build it: + +```sh +git clone git@github.com:stainless-sdks/imagekit-typescript.git +cd imagekit-typescript +./scripts/bootstrap +./scripts/build +``` + +### Running + +```sh +# set env vars as needed +export IMAGEKIT_PRIVATE_API_KEY="My Private API Key" +export ORG_MY_PASSWORD_TOKEN="My Password" +node ./packages/mcp-server/dist/index.js +``` + +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y @imagekit/nodejs-mcp` + +### Via MCP Client + +[Build the project](#building) as mentioned above. + +There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already +have a client, consult their documentation to install the MCP server. + +For clients with a configuration JSON, it might look something like this: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "node", + "args": [ + "/path/to/local/imagekit-typescript/packages/mcp-server", + "--client=claude", + "--tools=dynamic" + ], + "env": { + "IMAGEKIT_PRIVATE_API_KEY": "My Private API Key", + "ORG_MY_PASSWORD_TOKEN": "My Password" + } + } + } +} +``` + +## Exposing endpoints to your MCP Client + +There are two ways to expose endpoints as tools in the MCP server: + +1. Exposing one tool per endpoint, and filtering as necessary +2. Exposing a set of tools to dynamically discover and invoke endpoints from the API + +### Filtering endpoints and tools + +You can run the package on the command line to discover and filter the set of tools that are exposed by the +MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's +context window. + +You can filter by multiple aspects: + +- `--tool` includes a specific tool by name +- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` +- `--operation` includes just read (get/list) or just write operations + +### Dynamic tools + +If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will +expose the following tools: + +1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query +2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint +3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters + +This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all +of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to +search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it +can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, +you can opt-in to explicit tools, the dynamic tools, or both. + +See more information with `--help`. + +All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). + +Use `--list` to see the list of available tools, or see below. + +### Specifying the MCP Client + +Different clients have varying abilities to handle arbitrary tools and schemas. + +You can specify the client you are using with the `--client` argument, and the MCP server will automatically +serve tools and schemas that are more compatible with that client. + +- `--client=`: Set all capabilities based on a known MCP client + + - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` + - Example: `--client=cursor` + +Additionally, if you have a client not on the above list, or the client has gotten better +over time, you can manually enable or disable certain capabilities: + +- `--capability=`: Specify individual client capabilities + - Available capabilities: + - `top-level-unions`: Enable support for top-level unions in tool schemas + - `valid-json`: Enable JSON string parsing for arguments + - `refs`: Enable support for $ref pointers in schemas + - `unions`: Enable support for union types (anyOf) in schemas + - `formats`: Enable support for format validations in schemas (e.g. date-time, email) + - `tool-name-length=N`: Set maximum tool name length to N characters + - Example: `--capability=top-level-unions --capability=tool-name-length=40` + - Example: `--capability=top-level-unions,tool-name-length=40` + +### Examples + +1. Filter for read operations on cards: + +```bash +--resource=cards --operation=read +``` + +2. Exclude specific tools while including others: + +```bash +--resource=cards --no-tool=create_cards +``` + +3. Configure for Cursor client with custom max tool name length: + +```bash +--client=cursor --capability=tool-name-length=40 +``` + +4. Complex filtering with multiple criteria: + +```bash +--resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards +``` + +## Running remotely + +Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket. + +Authorization can be provided via the `Authorization` header using the Basic scheme. + +Additionally, authorization can be provided via the following headers: +| Header | Equivalent client option | Security scheme | +| ---------------------------- | ------------------------ | --------------- | +| `x-imagekit-private-api-key` | `privateAPIKey` | basicAuth | +| `x-org-my-password-token` | `password` | basicAuth | + +A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "url": "http://localhost:3000", + "headers": { + "Authorization": "Basic " + } + } + } +} +``` + +The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. +For example, to exclude specific tools while including others, use the URL: + +``` +http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards +``` + +Or, to configure for the Cursor client, with a custom max tool name length, use the URL: + +``` +http://localhost:3000?client=cursor&capability=tool-name-length%3D40 +``` + +## Importing the tools and server individually + +```js +// Import the server, generated endpoints, or the init function +import { server, endpoints, init } from "@imagekit/nodejs-mcp/server"; + +// import a specific tool +import createCustomMetadataFields from "@imagekit/nodejs-mcp/tools/custom-metadata-fields/create-custom-metadata-fields"; + +// initialize the server and all endpoints +init({ server, endpoints }); + +// manually start server +const transport = new StdioServerTransport(); +await server.connect(transport); + +// or initialize your own server with specific tools +const myServer = new McpServer(...); + +// define your own endpoint +const myCustomEndpoint = { + tool: { + name: 'my_custom_tool', + description: 'My custom tool', + inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), + }, + handler: async (client: client, args: any) => { + return { myResponse: 'Hello world!' }; + }) +}; + +// initialize the server with your custom endpoints +init({ server: myServer, endpoints: [createCustomMetadataFields, myCustomEndpoint] }); +``` + +## Available Tools + +The following tools are available in this MCP server. + +### Resource `customMetadataFields`: + +- `create_custom_metadata_fields` (`write`): This API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API. +- `update_custom_metadata_fields` (`write`): This API updates the label or schema of an existing custom metadata field. +- `list_custom_metadata_fields` (`read`): This API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response. +- `delete_custom_metadata_fields` (`write`): This API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name. + +### Resource `files`: + +- `update_files` (`write`): This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API. +- `delete_files` (`write`): This API deletes the file and all its file versions permanently. + + Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. + +- `copy_files` (`write`): This will copy a file from one folder to another. + + Note: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history. + +- `get_files` (`read`): This API returns an object with details or attributes about the current version of the file. +- `move_files` (`write`): This will move a file and all its versions from one folder to another. + + Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file. + +- `rename_files` (`write`): You can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. + + Note: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested. + +- `upload_files` (`write`): ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload. + + The [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT. + + **File size limit** \ + On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans. + + **Version limit** \ + A file can have a maximum of 100 versions. + + **Demo applications** + + - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. + - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. + +### Resource `files.bulk`: + +- `delete_files_bulk` (`write`): This API deletes multiple files and all their file versions permanently. + + Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. + + A maximum of 100 files can be deleted at a time. + +- `add_tags_files_bulk` (`write`): This API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time. +- `remove_ai_tags_files_bulk` (`write`): This API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time. +- `remove_tags_files_bulk` (`write`): This API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time. + +### Resource `files.versions`: + +- `list_files_versions` (`read`): This API returns details of all versions of a file. +- `delete_files_versions` (`write`): This API deletes a non-current file version permanently. The API returns an empty response. + + Note: If you want to delete all versions of a file, use the delete file API. + +- `get_files_versions` (`read`): This API returns an object with details or attributes of a file version. +- `restore_files_versions` (`write`): This API restores a file version as the current file version. + +### Resource `files.metadata`: + +- `get_files_metadata` (`read`): You can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API. + + You can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter. + +- `get_from_url_files_metadata` (`read`): Get image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API. + +### Resource `assets`: + +- `list_assets` (`read`): This API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`. + +### Resource `cache.invalidation`: + +- `create_cache_invalidation` (`write`): This API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes. +- `get_cache_invalidation` (`read`): This API returns the status of a purge cache request. + +### Resource `folders`: + +- `create_folders` (`write`): This will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created. +- `delete_folders` (`write`): This will delete a folder and all its contents permanently. The API returns an empty response. +- `copy_folders` (`write`): This will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. +- `move_folders` (`write`): This will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. +- `rename_folders` (`write`): This API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name. + +### Resource `folders.job`: + +- `get_folders_job` (`read`): This API returns the status of a bulk job like copy and move folder operations. + +### Resource `accounts.usage`: + +- `get_accounts_usage` (`read`): Get the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date. + +### Resource `accounts.origins`: + +- `create_accounts_origins` (`write`): **Note:** This API is currently in beta. + Creates a new origin and returns the origin object. +- `update_accounts_origins` (`write`): **Note:** This API is currently in beta. + Updates the origin identified by `id` and returns the updated origin object. +- `list_accounts_origins` (`read`): **Note:** This API is currently in beta. + Returns an array of all configured origins for the current account. +- `delete_accounts_origins` (`write`): **Note:** This API is currently in beta. + Permanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error. +- `get_accounts_origins` (`read`): **Note:** This API is currently in beta. + Retrieves the origin identified by `id`. + +### Resource `accounts.urlEndpoints`: + +- `create_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Creates a new URL‑endpoint and returns the resulting object. +- `update_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Updates the URL‑endpoint identified by `id` and returns the updated object. +- `list_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. + Returns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation. +- `delete_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Deletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation. +- `get_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. + Retrieves the URL‑endpoint identified by `id`. + +### Resource `beta.v2.files`: + +- `upload_v2_beta_files` (`write`): The V2 API enhances security by verifying the entire payload using JWT. This API is in beta. + + ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload. + + **File size limit** \ + On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans. + + **Version limit** \ + A file can have a maximum of 100 versions. + + **Demo applications** + + - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. + - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. diff --git a/packages/mcp-server/build b/packages/mcp-server/build new file mode 100644 index 00000000..2eede586 --- /dev/null +++ b/packages/mcp-server/build @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -exuo pipefail + +rm -rf dist; mkdir dist + +# Copy src to dist/src and build from dist/src into dist, so that +# the source map for index.js.map will refer to ./src/index.ts etc +cp -rp src README.md dist + +for file in LICENSE; do + if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi +done + +for file in CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done + +# this converts the export map paths for the dist directory +# and does a few other minor things +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json + +# updates the `@imagekit/nodejs` dependency to point to NPM +node scripts/postprocess-dist-package-json.cjs + +# build to .js/.mjs/.d.ts files +./node_modules/.bin/tsc-multi + +cp tsconfig.dist-src.json dist/src/tsconfig.json + +chmod +x dist/index.js + +DIST_PATH=./dist PKG_IMPORT_PATH=@imagekit/nodejs-mcp/ node ../../scripts/utils/postprocess-files.cjs diff --git a/packages/mcp-server/jest.config.ts b/packages/mcp-server/jest.config.ts new file mode 100644 index 00000000..6c2868f4 --- /dev/null +++ b/packages/mcp-server/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^@imagekit/nodejs-mcp$': '/src/index.ts', + '^@imagekit/nodejs-mcp/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json new file mode 100644 index 00000000..09d365b5 --- /dev/null +++ b/packages/mcp-server/package.json @@ -0,0 +1,85 @@ +{ + "name": "@imagekit/nodejs-mcp", + "version": "0.0.1-alpha.0", + "description": "The official MCP Server for the Image Kit API", + "author": "Image Kit ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": { + "type": "git", + "url": "git+https://github.com/stainless-sdks/imagekit-typescript.git", + "directory": "packages/mcp-server" + }, + "homepage": "https://github.com/stainless-sdks/imagekit-typescript/tree/main/packages/mcp-server#readme", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "private": false, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "build": "bash ./build", + "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "prettier --write --cache --cache-strategy metadata . !dist", + "prepare": "npm run build", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "eslint --ext ts,js .", + "fix": "eslint --fix --ext ts,js ." + }, + "dependencies": { + "@imagekit/nodejs": "file:../../dist/", + "@cloudflare/cabidela": "^0.2.4", + "@modelcontextprotocol/sdk": "^1.11.5", + "@valtown/deno-http-worker": "^0.0.21", + "cors": "^2.8.5", + "express": "^5.1.0", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "qs": "^6.14.0", + "yargs": "^17.7.2", + "zod": "^3.25.20", + "zod-to-json-schema": "^3.24.5", + "zod-validation-error": "^4.0.1" + }, + "bin": { + "mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jest": "^29.4.0", + "@types/qs": "^6.14.0", + "@types/yargs": "^17.0.8", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^8.49.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "ts-jest": "^29.1.0", + "ts-morph": "^19.0.0", + "ts-node": "^10.5.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsconfig-paths": "^4.0.0", + "typescript": "5.8.3" + }, + "imports": { + "@imagekit/nodejs-mcp": ".", + "@imagekit/nodejs-mcp/*": "./src/*" + }, + "exports": { + ".": { + "require": "./dist/index.js", + "default": "./dist/index.mjs" + }, + "./*.mjs": "./dist/*.mjs", + "./*.js": "./dist/*.js", + "./*": { + "require": "./dist/*.js", + "default": "./dist/*.mjs" + } + } +} diff --git a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs new file mode 100644 index 00000000..2c75a6cd --- /dev/null +++ b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs @@ -0,0 +1,12 @@ +const fs = require('fs'); +const pkgJson = require('../dist/package.json'); +const parentPkgJson = require('../../../package.json'); + +for (const dep in pkgJson.dependencies) { + // ensure we point to NPM instead of a local directory + if (dep === '@imagekit/nodejs') { + pkgJson.dependencies[dep] = '^' + parentPkgJson.version; + } +} + +fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2)); diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts new file mode 100644 index 00000000..15ce7f55 --- /dev/null +++ b/packages/mcp-server/src/code-tool-paths.cts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts new file mode 100644 index 00000000..02e7e890 --- /dev/null +++ b/packages/mcp-server/src/code-tool-types.ts @@ -0,0 +1,14 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ClientOptions } from '@imagekit/nodejs'; + +export type WorkerInput = { + opts: ClientOptions; + code: string; +}; +export type WorkerSuccess = { + result: unknown | null; + logLines: string[]; + errLines: string[]; +}; +export type WorkerError = { message: string | undefined }; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts new file mode 100644 index 00000000..865c3928 --- /dev/null +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -0,0 +1,46 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import util from 'node:util'; +import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; +import { ImageKit } from '@imagekit/nodejs'; + +const fetch = async (req: Request): Promise => { + const { opts, code } = (await req.json()) as WorkerInput; + const client = new ImageKit({ + ...opts, + }); + + const logLines: string[] = []; + const errLines: string[] = []; + const console = { + log: (...args: unknown[]) => { + logLines.push(util.format(...args)); + }, + error: (...args: unknown[]) => { + errLines.push(util.format(...args)); + }, + }; + try { + let run_ = async (client: any) => {}; + eval(` + ${code} + run_ = run; + `); + const result = await run_(client); + return Response.json({ + result, + logLines, + errLines, + } satisfies WorkerSuccess); + } catch (e) { + const message = e instanceof Error ? e.message : undefined; + return Response.json( + { + message, + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } +}; + +export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts new file mode 100644 index 00000000..e0e2d2e7 --- /dev/null +++ b/packages/mcp-server/src/code-tool.ts @@ -0,0 +1,145 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { dirname } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import ImageKit, { ClientOptions } from '@imagekit/nodejs'; +import { Endpoint, ContentBlock, Metadata } from './tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +import { newDenoHTTPWorker } from '@valtown/deno-http-worker'; +import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; +import { workerPath } from './code-tool-paths.cjs'; + +/** + * A tool that runs code against a copy of the SDK. + * + * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export function codeTool(): Endpoint { + const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; + const tool: Tool = { + name: 'execute', + description: + 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, + }; + + const handler = async (client: ImageKit, args: unknown) => { + const baseURLHostname = new URL(client.baseURL).hostname; + const { code } = args as { code: string }; + + const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { + runFlags: [ + `--node-modules-dir=manual`, + `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + `--allow-net=${baseURLHostname}`, + // Allow environment variables because instantiating the client will try to read from them, + // even though they are not set. + '--allow-env', + ], + printOutput: true, + spawnOptions: { + cwd: dirname(workerPath), + }, + }); + + try { + const resp = await new Promise((resolve, reject) => { + worker.addEventListener('exit', (exitCode) => { + reject(new Error(`Worker exited with code ${exitCode}`)); + }); + + const opts: ClientOptions = { + baseURL: client.baseURL, + privateAPIKey: client.privateAPIKey, + password: client.password, + defaultHeaders: { + 'X-Stainless-MCP': 'true', + }, + }; + + const req = worker.request( + 'http://localhost', + { + headers: { + 'content-type': 'application/json', + }, + method: 'POST', + }, + (resp) => { + const body: Uint8Array[] = []; + resp.on('error', (err) => { + reject(err); + }); + resp.on('data', (chunk) => { + body.push(chunk); + }); + resp.on('end', () => { + resolve( + new Response(Buffer.concat(body).toString(), { + status: resp.statusCode ?? 200, + headers: resp.headers as any, + }), + ); + }); + }, + ); + + const body = JSON.stringify({ + opts, + code, + } satisfies WorkerInput); + + req.write(body, (err) => { + if (err !== null && err !== undefined) { + reject(err); + } + }); + + req.end(); + }); + + if (resp.status === 200) { + const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; + const returnOutput: ContentBlock | null = + result === null ? null + : result === undefined ? null + : { + type: 'text', + text: typeof result === 'string' ? (result as string) : JSON.stringify(result), + }; + const logOutput: ContentBlock | null = + logLines.length === 0 ? + null + : { + type: 'text', + text: logLines.join('\n'), + }; + const errOutput: ContentBlock | null = + errLines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + errLines.join('\n'), + }; + return { + content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), + }; + } else { + const { message } = (await resp.json()) as WorkerError; + throw new Error(message); + } + } catch (e) { + throw e; + } finally { + worker.terminate(); + } + }; + + return { metadata, tool, handler }; +} diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts new file mode 100644 index 00000000..f84053c7 --- /dev/null +++ b/packages/mcp-server/src/compat.ts @@ -0,0 +1,483 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; +import { Endpoint } from './tools'; + +export interface ClientCapabilities { + topLevelUnions: boolean; + validJson: boolean; + refs: boolean; + unions: boolean; + formats: boolean; + toolNameLength: number | undefined; +} + +export const defaultClientCapabilities: ClientCapabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, +}; + +export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); +export type ClientType = z.infer; + +// Client presets for compatibility +// Note that these could change over time as models get better, so this is +// a best effort. +export const knownClients: Record, ClientCapabilities> = { + 'openai-agents': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + claude: { + topLevelUnions: true, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + 'claude-code': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + cursor: { + topLevelUnions: false, + validJson: true, + refs: false, + unions: false, + formats: false, + toolNameLength: 50, + }, +}; + +/** + * Attempts to parse strings into JSON objects + */ +export function parseEmbeddedJSON(args: Record, schema: Record) { + let updated = false; + const newArgs: Record = Object.assign({}, args); + + for (const [key, value] of Object.entries(newArgs)) { + if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + // Only parse if result is a plain object (not array, null, or primitive) + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + newArgs[key] = parsed; + updated = true; + } + } catch (e) { + // Not valid JSON, leave as is + } + } + } + + if (updated) { + return newArgs; + } + + return args; +} + +export type JSONSchema = { + type?: string; + properties?: Record; + required?: string[]; + anyOf?: JSONSchema[]; + $ref?: string; + $defs?: Record; + [key: string]: any; +}; + +/** + * Truncates tool names to the specified length while ensuring uniqueness. + * If truncation would cause duplicate names, appends a number to make them unique. + */ +export function truncateToolNames(names: string[], maxLength: number): Map { + if (maxLength <= 0) { + return new Map(); + } + + const renameMap = new Map(); + const usedNames = new Set(); + + const toTruncate = names.filter((name) => name.length > maxLength); + + if (toTruncate.length === 0) { + return renameMap; + } + + const willCollide = + new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; + + if (!willCollide) { + for (const name of toTruncate) { + const truncatedName = name.slice(0, maxLength); + renameMap.set(name, truncatedName); + } + } else { + const baseLength = maxLength - 1; + + for (const name of toTruncate) { + const baseName = name.slice(0, baseLength); + let counter = 1; + + while (usedNames.has(baseName + counter)) { + counter++; + } + + const finalName = baseName + counter; + renameMap.set(name, finalName); + usedNames.add(finalName); + } + } + + return renameMap; +} + +/** + * Removes top-level unions from a tool by splitting it into multiple tools, + * one for each variant in the union. + */ +export function removeTopLevelUnions(tool: Tool): Tool[] { + const inputSchema = tool.inputSchema as JSONSchema; + const variants = inputSchema.anyOf; + + if (!variants || !Array.isArray(variants) || variants.length === 0) { + return [tool]; + } + + const defs = inputSchema.$defs || {}; + + return variants.map((variant, index) => { + const variantSchema: JSONSchema = { + ...inputSchema, + ...variant, + type: 'object', + properties: { + ...(inputSchema.properties || {}), + ...(variant.properties || {}), + }, + }; + + delete variantSchema.anyOf; + + if (!variantSchema['description']) { + variantSchema['description'] = tool.description; + } + + const usedDefs = findUsedDefs(variant, defs); + if (Object.keys(usedDefs).length > 0) { + variantSchema.$defs = usedDefs; + } else { + delete variantSchema.$defs; + } + + return { + ...tool, + name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, + description: variant['description'] || tool.description, + inputSchema: variantSchema, + } as Tool; + }); +} + +function findUsedDefs( + schema: JSONSchema, + defs: Record, + visited: Set = new Set(), +): Record { + const usedDefs: Record = {}; + + if (typeof schema !== 'object' || schema === null) { + return usedDefs; + } + + if (schema.$ref) { + const refParts = schema.$ref.split('/'); + if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { + const defName = refParts[2]; + const def = defs[defName]; + if (def && !visited.has(schema.$ref)) { + usedDefs[defName] = def; + visited.add(schema.$ref); + Object.assign(usedDefs, findUsedDefs(def, defs, visited)); + visited.delete(schema.$ref); + } + } + return usedDefs; + } + + for (const key in schema) { + if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { + Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); + } + } + + return usedDefs; +} + +// Export for testing +export { findUsedDefs }; + +/** + * Inlines all $refs in a schema, eliminating $defs. + * If a circular reference is detected, the circular property is removed. + */ +export function inlineRefs(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + const clonedSchema = { ...schema }; + const defs: Record = schema.$defs || {}; + + delete clonedSchema.$defs; + + const result = inlineRefsRecursive(clonedSchema, defs, new Set()); + // The top level can never be null + return result === null ? {} : result; +} + +function inlineRefsRecursive( + schema: JSONSchema, + defs: Record, + refPath: Set, +): JSONSchema | null { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => { + const processed = inlineRefsRecursive(item, defs, refPath); + return processed === null ? {} : processed; + }) as JSONSchema; + } + + const result = { ...schema }; + + if ('$ref' in result && typeof result.$ref === 'string') { + if (result.$ref.startsWith('#/$defs/')) { + const refName = result.$ref.split('/').pop() as string; + const def = defs[refName]; + + // If we've already seen this ref in our path, we have a circular reference + if (refPath.has(result.$ref)) { + // For circular references, we completely remove the property + // by returning null. The parent will remove it. + return null; + } + + if (def) { + const newRefPath = new Set(refPath); + newRefPath.add(result.$ref); + + const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); + + if (inlinedDef === null) { + return { ...result }; + } + + // Merge the inlined definition with the original schema's properties + // but preserve things like description, etc. + const { $ref, ...rest } = result; + return { ...inlinedDef, ...rest }; + } + } + + // Keep external refs as-is + return result; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); + if (processed === null) { + // Remove properties that would cause circular references + delete result[key]; + } else { + result[key] = processed; + } + } + } + + return result; +} + +/** + * Removes anyOf fields from a schema, using only the first variant. + */ +export function removeAnyOf(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeAnyOf(item)) as JSONSchema; + } + + const result = { ...schema }; + + if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { + const firstVariant = result.anyOf[0]; + + if (firstVariant && typeof firstVariant === 'object') { + // Special handling for properties to ensure deep merge + if (firstVariant.properties && result.properties) { + result.properties = { + ...result.properties, + ...(firstVariant.properties as Record), + }; + } else if (firstVariant.properties) { + result.properties = { ...firstVariant.properties }; + } + + for (const key in firstVariant) { + if (key !== 'properties') { + result[key] = firstVariant[key]; + } + } + } + + delete result.anyOf; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeAnyOf(result[key] as JSONSchema); + } + } + + return result; +} + +/** + * Removes format fields from a schema and appends them to the description. + */ +export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { + if (formatsCapability) { + return schema; + } + + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; + } + + const result = { ...schema }; + + if ('format' in result && typeof result['format'] === 'string') { + const formatStr = `(format: "${result['format']}")`; + + if ('description' in result && typeof result['description'] === 'string') { + result['description'] = `${result['description']} ${formatStr}`; + } else { + result['description'] = formatStr; + } + + delete result['format']; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); + } + } + + return result; +} + +/** + * Applies all compatibility transformations to the endpoints based on the provided capabilities. + */ +export function applyCompatibilityTransformations( + endpoints: Endpoint[], + capabilities: ClientCapabilities, +): Endpoint[] { + let transformedEndpoints = [...endpoints]; + + // Handle top-level unions first as this changes tool names + if (!capabilities.topLevelUnions) { + const newEndpoints: Endpoint[] = []; + + for (const endpoint of transformedEndpoints) { + const variantTools = removeTopLevelUnions(endpoint.tool); + + if (variantTools.length === 1) { + newEndpoints.push(endpoint); + } else { + for (const variantTool of variantTools) { + newEndpoints.push({ + ...endpoint, + tool: variantTool, + }); + } + } + } + + transformedEndpoints = newEndpoints; + } + + if (capabilities.toolNameLength) { + const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); + const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); + + transformedEndpoints = transformedEndpoints.map((endpoint) => ({ + ...endpoint, + tool: { + ...endpoint.tool, + name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, + }, + })); + } + + if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { + transformedEndpoints = transformedEndpoints.map((endpoint) => { + let schema = endpoint.tool.inputSchema as JSONSchema; + + if (!capabilities.refs) { + schema = inlineRefs(schema); + } + + if (!capabilities.unions) { + schema = removeAnyOf(schema); + } + + if (!capabilities.formats) { + schema = removeFormats(schema, capabilities.formats); + } + + return { + ...endpoint, + tool: { + ...endpoint.tool, + inputSchema: schema as typeof endpoint.tool.inputSchema, + }, + }; + }); + } + + return transformedEndpoints; +} + +function toSnakeCase(str: string): string { + return str + .replace(/\s+/g, '_') + .replace(/([a-z])([A-Z])/g, '$1_$2') + .toLowerCase(); +} diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts new file mode 100644 index 00000000..47d60e0d --- /dev/null +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -0,0 +1,153 @@ +import ImageKit from '@imagekit/nodejs'; +import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { z } from 'zod'; +import { Cabidela } from '@cloudflare/cabidela'; + +function zodToInputSchema(schema: z.ZodSchema) { + return { + type: 'object' as const, + ...(zodToJsonSchema(schema) as any), + }; +} + +/** + * A list of tools that expose all the endpoints in the API dynamically. + * + * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { + const listEndpointsSchema = z.object({ + search_query: z + .string() + .optional() + .describe( + 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', + ), + }); + + const listEndpointsTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'list_api_endpoints', + description: 'List or search for all endpoints in the Image Kit TypeScript API', + inputSchema: zodToInputSchema(listEndpointsSchema), + }, + handler: async (client: ImageKit, args: Record | undefined): Promise => { + const query = args && listEndpointsSchema.parse(args).search_query?.trim(); + + const filteredEndpoints = + query && query.length > 0 ? + endpoints.filter((endpoint) => { + const fieldsToMatch = [ + endpoint.tool.name, + endpoint.tool.description, + endpoint.metadata.resource, + endpoint.metadata.operation, + ...endpoint.metadata.tags, + ]; + return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); + }) + : endpoints; + + return asTextContentResult({ + tools: filteredEndpoints.map(({ tool, metadata }) => ({ + name: tool.name, + description: tool.description, + resource: metadata.resource, + operation: metadata.operation, + tags: metadata.tags, + })), + }); + }, + }; + + const getEndpointSchema = z.object({ + endpoint: z.string().describe('The name of the endpoint to get the schema for.'), + }); + const getEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'get_api_endpoint_schema', + description: + 'Get the schema for an endpoint in the Image Kit TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', + inputSchema: zodToInputSchema(getEndpointSchema), + }, + handler: async (client: ImageKit, args: Record | undefined) => { + if (!args) { + throw new Error('No endpoint provided'); + } + const endpointName = getEndpointSchema.parse(args).endpoint; + + const endpoint = endpoints.find((e) => e.tool.name === endpointName); + if (!endpoint) { + throw new Error(`Endpoint ${endpointName} not found`); + } + return asTextContentResult(endpoint.tool); + }, + }; + + const invokeEndpointSchema = z.object({ + endpoint_name: z.string().describe('The name of the endpoint to invoke.'), + args: z + .record(z.string(), z.any()) + .describe( + 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', + ), + }); + + const invokeEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'write' as const, + tags: [], + }, + tool: { + name: 'invoke_api_endpoint', + description: + 'Invoke an endpoint in the Image Kit TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', + inputSchema: zodToInputSchema(invokeEndpointSchema), + }, + handler: async (client: ImageKit, args: Record | undefined): Promise => { + if (!args) { + throw new Error('No endpoint provided'); + } + const { success, data, error } = invokeEndpointSchema.safeParse(args); + if (!success) { + throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); + } + const { endpoint_name, args: endpointArgs } = data; + + const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); + if (!endpoint) { + throw new Error( + `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, + ); + } + + try { + // Try to validate the arguments for a better error message + const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); + cabidela.validate(endpointArgs); + } catch (error) { + throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); + } + + return await endpoint.handler(client, endpointArgs); + }, + }; + + return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; +} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts new file mode 100644 index 00000000..1aa9a40c --- /dev/null +++ b/packages/mcp-server/src/filtering.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +import initJq from 'jq-web'; + +export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { + if (jqFilter && typeof jqFilter === 'string') { + return await jq(response, jqFilter); + } else { + return response; + } +} + +async function jq(json: any, jqFilter: string) { + return (await initJq).json(json, jqFilter); +} diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts new file mode 100644 index 00000000..d5162bfa --- /dev/null +++ b/packages/mcp-server/src/headers.ts @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { IncomingMessage } from 'node:http'; +import { ClientOptions } from '@imagekit/nodejs'; + +export const parseAuthHeaders = (req: IncomingMessage): Partial => { + if (req.headers.authorization) { + const scheme = req.headers.authorization.split(' ')[0]!; + const value = req.headers.authorization.slice(scheme.length + 1); + switch (scheme) { + case 'Basic': + const rawValue = Buffer.from(value, 'base64').toString(); + return { + privateAPIKey: rawValue.slice(0, rawValue.search(':')), + password: rawValue.slice(rawValue.search(':') + 1), + }; + default: + throw new Error(`Unsupported authorization scheme`); + } + } + + const privateAPIKey = + Array.isArray(req.headers['x-imagekit-private-api-key']) ? + req.headers['x-imagekit-private-api-key'][0] + : req.headers['x-imagekit-private-api-key']; + const password = + Array.isArray(req.headers['x-org-my-password-token']) ? + req.headers['x-org-my-password-token'][0] + : req.headers['x-org-my-password-token']; + return { privateAPIKey, password }; +}; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts new file mode 100644 index 00000000..c11185b7 --- /dev/null +++ b/packages/mcp-server/src/http.ts @@ -0,0 +1,115 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; + +import express from 'express'; +import { fromError } from 'zod-validation-error/v3'; +import { McpOptions, parseQueryOptions } from './options'; +import { initMcpServer, newMcpServer } from './server'; +import { parseAuthHeaders } from './headers'; + +const newServer = ( + defaultMcpOptions: McpOptions, + req: express.Request, + res: express.Response, +): McpServer | null => { + const server = newMcpServer(); + + let mcpOptions: McpOptions; + try { + mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); + } catch (error) { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: `Invalid request: ${fromError(error)}`, + }, + }); + return null; + } + + try { + const authOptions = parseAuthHeaders(req); + initMcpServer({ + server: server, + clientOptions: { + ...authOptions, + defaultHeaders: { + 'X-Stainless-MCP': 'true', + }, + }, + mcpOptions, + }); + } catch { + res.status(401).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Unauthorized', + }, + }); + return null; + } + + return server; +}; + +const post = (defaultOptions: McpOptions) => async (req: express.Request, res: express.Response) => { + const server = newServer(defaultOptions, req, res); + // If we return null, we already set the authorization error. + if (server === null) return; + const transport = new StreamableHTTPServerTransport({ + // Stateless server + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); +}; + +const get = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +const del = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +export const streamableHTTPApp = (options: McpOptions): express.Express => { + const app = express(); + app.set('query parser', 'extended'); + app.use(express.json()); + + app.get('/', get); + app.post('/', post(options)); + app.delete('/', del); + + return app; +}; + +export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { + const app = streamableHTTPApp(options); + const server = app.listen(port); + const address = server.address(); + + if (typeof address === 'string') { + console.error(`MCP Server running on streamable HTTP at ${address}`); + } else if (address !== null) { + console.error(`MCP Server running on streamable HTTP on port ${address.port}`); + } else { + console.error(`MCP Server running on streamable HTTP on port ${port}`); + } +}; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts new file mode 100644 index 00000000..c450e4bb --- /dev/null +++ b/packages/mcp-server/src/index.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +import { selectTools } from './server'; +import { Endpoint, endpoints } from './tools'; +import { McpOptions, parseCLIOptions } from './options'; +import { launchStdioServer } from './stdio'; +import { launchStreamableHTTPServer } from './http'; + +async function main() { + const options = parseOptionsOrError(); + + if (options.list) { + listAllTools(); + return; + } + + const selectedTools = selectToolsOrError(endpoints, options); + + console.error( + `MCP Server starting with ${selectedTools.length} tools:`, + selectedTools.map((e) => e.tool.name), + ); + + switch (options.transport) { + case 'stdio': + await launchStdioServer(options); + break; + case 'http': + await launchStreamableHTTPServer(options, options.port ?? options.socket); + break; + } +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error in main():', error); + process.exit(1); + }); +} + +function parseOptionsOrError() { + try { + return parseCLIOptions(); + } catch (error) { + console.error('Error parsing options:', error); + process.exit(1); + } +} + +function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Endpoint[] { + try { + const includedTools = selectTools(endpoints, options); + if (includedTools.length === 0) { + console.error('No tools match the provided filters.'); + process.exit(1); + } + return includedTools; + } catch (error) { + if (error instanceof Error) { + console.error('Error filtering tools:', error.message); + } else { + console.error('Error filtering tools:', error); + } + process.exit(1); + } +} + +function listAllTools() { + if (endpoints.length === 0) { + console.log('No tools available.'); + return; + } + console.log('Available tools:\n'); + + // Group endpoints by resource + const resourceGroups = new Map(); + + for (const endpoint of endpoints) { + const resource = endpoint.metadata.resource; + if (!resourceGroups.has(resource)) { + resourceGroups.set(resource, []); + } + resourceGroups.get(resource)!.push(endpoint); + } + + // Sort resources alphabetically + const sortedResources = Array.from(resourceGroups.keys()).sort(); + + // Display hierarchically by resource + for (const resource of sortedResources) { + console.log(`Resource: ${resource}`); + + const resourceEndpoints = resourceGroups.get(resource)!; + // Sort endpoints by tool name + resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); + + for (const endpoint of resourceEndpoints) { + const { + tool, + metadata: { operation, tags }, + } = endpoint; + + console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); + console.log(` Description: ${tool.description}`); + } + console.log(''); + } +} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts new file mode 100644 index 00000000..2100cf58 --- /dev/null +++ b/packages/mcp-server/src/options.ts @@ -0,0 +1,456 @@ +import qs from 'qs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import z from 'zod'; +import { endpoints, Filter } from './tools'; +import { ClientCapabilities, knownClients, ClientType } from './compat'; + +export type CLIOptions = McpOptions & { + list: boolean; + transport: 'stdio' | 'http'; + port: number | undefined; + socket: string | undefined; +}; + +export type McpOptions = { + client?: ClientType | undefined; + includeDynamicTools?: boolean | undefined; + includeAllTools?: boolean | undefined; + includeCodeTools?: boolean | undefined; + filters?: Filter[] | undefined; + capabilities?: Partial | undefined; +}; + +const CAPABILITY_CHOICES = [ + 'top-level-unions', + 'valid-json', + 'refs', + 'unions', + 'formats', + 'tool-name-length', +] as const; + +type Capability = (typeof CAPABILITY_CHOICES)[number]; + +function parseCapabilityValue(cap: string): { name: Capability; value?: number } { + if (cap.startsWith('tool-name-length=')) { + const parts = cap.split('='); + if (parts.length === 2) { + const length = parseInt(parts[1]!, 10); + if (!isNaN(length)) { + return { name: 'tool-name-length', value: length }; + } + throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); + } + throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); + } + if (!CAPABILITY_CHOICES.includes(cap as Capability)) { + throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); + } + return { name: cap as Capability }; +} + +export function parseCLIOptions(): CLIOptions { + const opts = yargs(hideBin(process.argv)) + .option('tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code'], + description: 'Use dynamic tools or all tools', + }) + .option('no-tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code'], + description: 'Do not use any dynamic or all tools', + }) + .option('tool', { + type: 'string', + array: true, + description: 'Include tools matching the specified names', + }) + .option('resource', { + type: 'string', + array: true, + description: 'Include tools matching the specified resources', + }) + .option('operation', { + type: 'string', + array: true, + choices: ['read', 'write'], + description: 'Include tools matching the specified operations', + }) + .option('tag', { + type: 'string', + array: true, + description: 'Include tools with the specified tags', + }) + .option('no-tool', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified names', + }) + .option('no-resource', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified resources', + }) + .option('no-operation', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified operations', + }) + .option('no-tag', { + type: 'string', + array: true, + description: 'Exclude tools with the specified tags', + }) + .option('list', { + type: 'boolean', + description: 'List all tools and exit', + }) + .option('client', { + type: 'string', + choices: Object.keys(knownClients), + description: 'Specify the MCP client being used', + }) + .option('capability', { + type: 'string', + array: true, + description: 'Specify client capabilities', + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('no-capability', { + type: 'string', + array: true, + description: 'Unset client capabilities', + choices: CAPABILITY_CHOICES, + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('describe-capabilities', { + type: 'boolean', + description: 'Print detailed explanation of client capabilities and exit', + }) + .option('transport', { + type: 'string', + choices: ['stdio', 'http'], + default: 'stdio', + description: 'What transport to use; stdio for local servers or http for remote servers', + }) + .option('port', { + type: 'number', + description: 'Port to serve on if using http transport', + }) + .option('socket', { + type: 'string', + description: 'Unix socket to serve on if using http transport', + }) + .help(); + + for (const [command, desc] of examples()) { + opts.example(command, desc); + } + + const argv = opts.parseSync(); + + // Handle describe-capabilities flag + if (argv.describeCapabilities) { + console.log(getCapabilitiesExplanation()); + process.exit(0); + } + + const filters: Filter[] = []; + + // Helper function to support comma-separated values + const splitValues = (values: string[] | undefined): string[] => { + if (!values) return []; + return values.flatMap((v) => v.split(',')); + }; + + for (const tag of splitValues(argv.tag)) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + + for (const tag of splitValues(argv.noTag)) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + + for (const resource of splitValues(argv.resource)) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + + for (const resource of splitValues(argv.noResource)) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + + for (const tool of splitValues(argv.tool)) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + + for (const tool of splitValues(argv.noTool)) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + for (const operation of splitValues(argv.operation)) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + + for (const operation of splitValues(argv.noOperation)) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + + // Parse client capabilities + const clientCapabilities: Partial = {}; + + // Apply individual capability overrides + if (Array.isArray(argv.capability)) { + for (const cap of argv.capability) { + const parsedCap = parseCapabilityValue(cap); + if (parsedCap.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsedCap.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsedCap.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsedCap.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsedCap.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsedCap.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsedCap.value; + } + } + } + + // Handle no-capability options to unset capabilities + if (Array.isArray(argv.noCapability)) { + for (const cap of argv.noCapability) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + } + + const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code') => + explicitTools ? argv.tools?.includes(toolType) && !argv.noTools?.includes(toolType) : undefined; + + const explicitTools = Boolean(argv.tools || argv.noTools); + const includeDynamicTools = shouldIncludeToolType('dynamic'); + const includeAllTools = shouldIncludeToolType('all'); + const includeCodeTools = shouldIncludeToolType('code'); + + const transport = argv.transport as 'stdio' | 'http'; + + const client = argv.client as ClientType; + return { + client: client && client !== 'infer' && knownClients[client] ? client : undefined, + includeDynamicTools, + includeAllTools, + includeCodeTools, + filters, + capabilities: clientCapabilities, + list: argv.list || false, + transport, + port: argv.port, + socket: argv.socket, + }; +} + +const coerceArray = (zodType: T) => + z.preprocess( + (val) => + Array.isArray(val) ? val + : val ? [val] + : val, + z.array(zodType).optional(), + ); + +const QueryOptions = z.object({ + tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Use dynamic tools or all tools'), + no_tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Do not use dynamic tools or all tools'), + tool: coerceArray(z.string()).describe('Include tools matching the specified names'), + resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), + operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Include tools matching the specified operations', + ), + tag: coerceArray(z.string()).describe('Include tools with the specified tags'), + no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), + no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), + no_operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Exclude tools matching the specified operations', + ), + no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), + client: ClientType.optional().describe('Specify the MCP client being used'), + capability: coerceArray(z.string()).describe('Specify client capabilities'), + no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), +}); + +export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { + const queryObject = typeof query === 'string' ? qs.parse(query) : query; + const queryOptions = QueryOptions.parse(queryObject); + + const filters: Filter[] = [...(defaultOptions.filters ?? [])]; + + for (const resource of queryOptions.resource || []) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + for (const operation of queryOptions.operation || []) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + for (const tag of queryOptions.tag || []) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + for (const tool of queryOptions.tool || []) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + for (const resource of queryOptions.no_resource || []) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + for (const operation of queryOptions.no_operation || []) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + for (const tag of queryOptions.no_tag || []) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + for (const tool of queryOptions.no_tool || []) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + // Parse client capabilities + const clientCapabilities: Partial = { ...defaultOptions.capabilities }; + + for (const cap of queryOptions.capability || []) { + const parsed = parseCapabilityValue(cap); + if (parsed.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsed.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsed.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsed.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsed.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsed.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsed.value; + } + } + + for (const cap of queryOptions.no_capability || []) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + + let dynamicTools: boolean | undefined = + queryOptions.no_tools && !queryOptions.no_tools?.includes('dynamic') ? false + : queryOptions.tools?.includes('dynamic') ? true + : defaultOptions.includeDynamicTools; + + let allTools: boolean | undefined = + queryOptions.no_tools && !queryOptions.no_tools?.includes('all') ? false + : queryOptions.tools?.includes('all') ? true + : defaultOptions.includeAllTools; + + return { + client: queryOptions.client ?? defaultOptions.client, + includeDynamicTools: dynamicTools, + includeAllTools: allTools, + includeCodeTools: undefined, + filters, + capabilities: clientCapabilities, + }; +} + +function getCapabilitiesExplanation(): string { + return ` +Client Capabilities Explanation: + +Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. + +When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. + +Available Capabilities: + +# top-level-unions +Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. + +# refs +Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. + +# valid-json +Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. + +# unions +Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. + +# formats +Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. + +# tool-name-length=N +Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. + +Client Presets (--client): +Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. + +Current presets: +${JSON.stringify(knownClients, null, 2)} + `; +} + +function examples(): [string, string][] { + const firstEndpoint = endpoints[0]!; + const secondEndpoint = + endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; + const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; + const otherEndpoint = secondEndpoint || firstEndpoint; + + return [ + [ + `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, + 'Include tools by name', + ], + [ + `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, + 'Filter by resource and operation', + ], + [ + `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, + 'Use resource wildcards and exclusions', + ], + [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], + [ + `--capability="top-level-unions" --capability="tool-name-length=40"`, + 'Specify individual client capabilities', + ], + [ + `--client="cursor" --no-capability="tool-name-length"`, + 'Use cursor client preset but remove tool name length limit', + ], + ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), + ]; +} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts new file mode 100644 index 00000000..d30dbdd5 --- /dev/null +++ b/packages/mcp-server/src/server.ts @@ -0,0 +1,180 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { Endpoint, endpoints, HandlerFunction, query } from './tools'; +import { + CallToolRequestSchema, + Implementation, + ListToolsRequestSchema, + Tool, +} from '@modelcontextprotocol/sdk/types.js'; +import { ClientOptions } from '@imagekit/nodejs'; +import ImageKit from '@imagekit/nodejs'; +import { + applyCompatibilityTransformations, + ClientCapabilities, + defaultClientCapabilities, + knownClients, + parseEmbeddedJSON, +} from './compat'; +import { dynamicTools } from './dynamic-tools'; +import { codeTool } from './code-tool'; +import { McpOptions } from './options'; + +export { McpOptions } from './options'; +export { ClientType } from './compat'; +export { Filter } from './tools'; +export { ClientOptions } from '@imagekit/nodejs'; +export { endpoints } from './tools'; + +export const newMcpServer = () => + new McpServer( + { + name: 'imagekit_nodejs_api', + version: '0.0.1-alpha.0', + }, + { capabilities: { tools: {}, logging: {} } }, + ); + +// Create server instance +export const server = newMcpServer(); + +/** + * Initializes the provided MCP Server with the given tools and handlers. + * If not provided, the default client, tools and handlers will be used. + */ +export function initMcpServer(params: { + server: Server | McpServer; + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}) { + const server = params.server instanceof McpServer ? params.server.server : params.server; + const mcpOptions = params.mcpOptions ?? {}; + + let providedEndpoints: Endpoint[] | null = null; + let endpointMap: Record | null = null; + + const initTools = (implementation?: Implementation) => { + if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { + mcpOptions.client = + implementation.name.toLowerCase().includes('claude') ? 'claude' + : implementation.name.toLowerCase().includes('cursor') ? 'cursor' + : undefined; + mcpOptions.capabilities = { + ...(mcpOptions.client && knownClients[mcpOptions.client]), + ...mcpOptions.capabilities, + }; + } + providedEndpoints = selectTools(endpoints, mcpOptions); + endpointMap = Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); + }; + + const logAtLevel = + (level: 'debug' | 'info' | 'warning' | 'error') => + (message: string, ...rest: unknown[]) => { + void server.sendLoggingMessage({ + level, + data: { message, rest }, + }); + }; + const logger = { + debug: logAtLevel('debug'), + info: logAtLevel('info'), + warn: logAtLevel('warning'), + error: logAtLevel('error'), + }; + + const client = new ImageKit({ + logger, + ...params.clientOptions, + defaultHeaders: { + ...params.clientOptions?.defaultHeaders, + 'X-Stainless-MCP': 'true', + }, + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + if (providedEndpoints === null) { + initTools(server.getClientVersion()); + } + return { + tools: providedEndpoints!.map((endpoint) => endpoint.tool), + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (endpointMap === null) { + initTools(server.getClientVersion()); + } + const { name, arguments: args } = request.params; + const endpoint = endpointMap![name]; + if (!endpoint) { + throw new Error(`Unknown tool: ${name}`); + } + + return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + }); +} + +/** + * Selects the tools to include in the MCP Server based on the provided options. + */ +export function selectTools(endpoints: Endpoint[], options?: McpOptions): Endpoint[] { + const filteredEndpoints = query(options?.filters ?? [], endpoints); + + let includedTools = filteredEndpoints; + + if (includedTools.length > 0) { + if (options?.includeDynamicTools) { + includedTools = dynamicTools(includedTools); + } + } else { + if (options?.includeAllTools) { + includedTools = endpoints; + } else if (options?.includeDynamicTools) { + includedTools = dynamicTools(endpoints); + } else if (options?.includeCodeTools) { + includedTools = [codeTool()]; + } else { + includedTools = endpoints; + } + } + + const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; + return applyCompatibilityTransformations(includedTools, capabilities); +} + +/** + * Runs the provided handler with the given client and arguments. + */ +export async function executeHandler( + tool: Tool, + handler: HandlerFunction, + client: ImageKit, + args: Record | undefined, + compatibilityOptions?: Partial, +) { + const options = { ...defaultClientCapabilities, ...compatibilityOptions }; + if (!options.validJson && args) { + args = parseEmbeddedJSON(args, tool.inputSchema); + } + return await handler(client, args || {}); +} + +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim(); + } else if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return; +}; + +export const readEnvOrError = (env: string): string => { + let envValue = readEnv(env); + if (envValue === undefined) { + throw new Error(`Environment variable ${env} is not set`); + } + return envValue; +}; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts new file mode 100644 index 00000000..d902a5bb --- /dev/null +++ b/packages/mcp-server/src/stdio.ts @@ -0,0 +1,13 @@ +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { initMcpServer, newMcpServer } from './server'; +import { McpOptions } from './options'; + +export const launchStdioServer = async (options: McpOptions) => { + const server = newMcpServer(); + + initMcpServer({ server, mcpOptions: options }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP Server running on stdio'); +}; diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts new file mode 100644 index 00000000..7e516de7 --- /dev/null +++ b/packages/mcp-server/src/tools.ts @@ -0,0 +1 @@ +export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts new file mode 100644 index 00000000..c189d4a2 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts @@ -0,0 +1,338 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/accounts/origins', + operationId: 'create-origin', +}; + +export const tool: Tool = { + name: 'create_accounts_origins', + description: + '**Note:** This API is currently in beta. \nCreates a new origin and returns the origin object.\n', + inputSchema: { + type: 'object', + properties: { + origin: { + $ref: '#/$defs/origin_request', + }, + }, + required: ['origin'], + $defs: { + origin_request: { + anyOf: [ + { + type: 'object', + title: 'S3', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'S3 Compatible', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + endpoint: { + type: 'string', + description: 'Custom S3-compatible endpoint.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3_COMPATIBLE'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + s3ForcePathStyle: { + type: 'boolean', + description: 'Use path-style S3 URLs?', + }, + }, + required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'Cloudinary Backup', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['CLOUDINARY_BACKUP'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'Web Folder', + properties: { + baseUrl: { + type: 'string', + description: 'Root URL for the web folder origin.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_FOLDER'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + forwardHostHeaderToOrigin: { + type: 'boolean', + description: 'Forward the Host header to origin?', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'name', 'type'], + }, + { + type: 'object', + title: 'Web Proxy', + properties: { + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_PROXY'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['name', 'type'], + }, + { + type: 'object', + title: 'Google Cloud Storage (GCS)', + properties: { + bucket: { + type: 'string', + }, + clientEmail: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + privateKey: { + type: 'string', + }, + type: { + type: 'string', + enum: ['GCS'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], + }, + { + type: 'object', + title: 'Azure Blob Storage', + properties: { + accountName: { + type: 'string', + }, + container: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + sasToken: { + type: 'string', + }, + type: { + type: 'string', + enum: ['AZURE_BLOB'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['accountName', 'container', 'name', 'sasToken', 'type'], + }, + { + type: 'object', + title: 'Akeneo PIM', + properties: { + baseUrl: { + type: 'string', + description: 'Akeneo instance base URL.', + }, + clientId: { + type: 'string', + description: 'Akeneo API client ID.', + }, + clientSecret: { + type: 'string', + description: 'Akeneo API client secret.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + password: { + type: 'string', + description: 'Akeneo API password.', + }, + type: { + type: 'string', + enum: ['AKENEO_PIM'], + }, + username: { + type: 'string', + description: 'Akeneo API username.', + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], + }, + ], + title: 'Origin request', + description: 'Schema for origin request resources.', + }, + }, + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.accounts.origins.create(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts new file mode 100644 index 00000000..d7c62e46 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'delete-origin', +}; + +export const tool: Tool = { + name: 'delete_accounts_origins', + description: + '**Note:** This API is currently in beta. \nPermanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + const response = await client.accounts.origins.delete(id).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts new file mode 100644 index 00000000..a155869e --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'get-origin', +}; + +export const tool: Tool = { + name: 'get_accounts_origins', + description: '**Note:** This API is currently in beta. \nRetrieves the origin identified by `id`.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.accounts.origins.get(id)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts new file mode 100644 index 00000000..1effce0d --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts @@ -0,0 +1,35 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/origins', + operationId: 'list-origins', +}; + +export const tool: Tool = { + name: 'list_accounts_origins', + description: + '**Note:** This API is currently in beta. \nReturns an array of all configured origins for the current account.\n', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + return asTextContentResult(await client.accounts.origins.list()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts new file mode 100644 index 00000000..a665117f --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts @@ -0,0 +1,345 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'update-origin', +}; + +export const tool: Tool = { + name: 'update_accounts_origins', + description: + '**Note:** This API is currently in beta. \nUpdates the origin identified by `id` and returns the updated origin object.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + origin: { + $ref: '#/$defs/origin_request', + }, + }, + required: ['id', 'origin'], + $defs: { + origin_request: { + anyOf: [ + { + type: 'object', + title: 'S3', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'S3 Compatible', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + endpoint: { + type: 'string', + description: 'Custom S3-compatible endpoint.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3_COMPATIBLE'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + s3ForcePathStyle: { + type: 'boolean', + description: 'Use path-style S3 URLs?', + }, + }, + required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'Cloudinary Backup', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['CLOUDINARY_BACKUP'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + title: 'Web Folder', + properties: { + baseUrl: { + type: 'string', + description: 'Root URL for the web folder origin.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_FOLDER'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + forwardHostHeaderToOrigin: { + type: 'boolean', + description: 'Forward the Host header to origin?', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'name', 'type'], + }, + { + type: 'object', + title: 'Web Proxy', + properties: { + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_PROXY'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['name', 'type'], + }, + { + type: 'object', + title: 'Google Cloud Storage (GCS)', + properties: { + bucket: { + type: 'string', + }, + clientEmail: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + privateKey: { + type: 'string', + }, + type: { + type: 'string', + enum: ['GCS'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], + }, + { + type: 'object', + title: 'Azure Blob Storage', + properties: { + accountName: { + type: 'string', + }, + container: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + sasToken: { + type: 'string', + }, + type: { + type: 'string', + enum: ['AZURE_BLOB'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['accountName', 'container', 'name', 'sasToken', 'type'], + }, + { + type: 'object', + title: 'Akeneo PIM', + properties: { + baseUrl: { + type: 'string', + description: 'Akeneo instance base URL.', + }, + clientId: { + type: 'string', + description: 'Akeneo API client ID.', + }, + clientSecret: { + type: 'string', + description: 'Akeneo API client secret.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + password: { + type: 'string', + description: 'Akeneo API password.', + }, + type: { + type: 'string', + enum: ['AKENEO_PIM'], + }, + username: { + type: 'string', + description: 'Akeneo API username.', + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], + }, + ], + title: 'Origin request', + description: 'Schema for origin request resources.', + }, + }, + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.accounts.origins.update(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts new file mode 100644 index 00000000..cc9742bb --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts @@ -0,0 +1,103 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/accounts/url-endpoints', + operationId: 'create-url-endpoint', +}; + +export const tool: Tool = { + name: 'create_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nCreates a new URL‑endpoint and returns the resulting object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + description: { + type: 'string', + description: 'Description of the URL endpoint.', + }, + origins: { + type: 'array', + description: + 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', + items: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + urlPrefix: { + type: 'string', + description: + 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', + }, + urlRewriter: { + anyOf: [ + { + type: 'object', + title: 'Cloudinary URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['CLOUDINARY'], + }, + preserveAssetDeliveryTypes: { + type: 'boolean', + description: 'Whether to preserve `/` in the rewritten URL.', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Imgix URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['IMGIX'], + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Akamai URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['AKAMAI'], + }, + }, + required: ['type'], + }, + ], + description: 'Configuration for third-party URL rewriting.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['description'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts new file mode 100644 index 00000000..c323a3c9 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'delete-url-endpoint', +}; + +export const tool: Tool = { + name: 'delete_accounts_url_endpoints', + description: + '**Note:** This API is currently in beta. \nDeletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + const response = await client.accounts.urlEndpoints.delete(id).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts new file mode 100644 index 00000000..98d9bba7 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts @@ -0,0 +1,49 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'get-url-endpoint', +}; + +export const tool: Tool = { + name: 'get_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nRetrieves the URL‑endpoint identified by `id`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts new file mode 100644 index 00000000..3d27514a --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/url-endpoints', + operationId: 'list-url-endpoints', +}; + +export const tool: Tool = { + name: 'list_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n },\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts new file mode 100644 index 00000000..ec8140dc --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts @@ -0,0 +1,112 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'update-url-endpoint', +}; + +export const tool: Tool = { + name: 'update_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nUpdates the URL‑endpoint identified by `id` and returns the updated object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + description: { + type: 'string', + description: 'Description of the URL endpoint.', + }, + origins: { + type: 'array', + description: + 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', + items: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + urlPrefix: { + type: 'string', + description: + 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', + }, + urlRewriter: { + anyOf: [ + { + type: 'object', + title: 'Cloudinary URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['CLOUDINARY'], + }, + preserveAssetDeliveryTypes: { + type: 'boolean', + description: 'Whether to preserve `/` in the rewritten URL.', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Imgix URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['IMGIX'], + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Akamai URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['AKAMAI'], + }, + }, + required: ['type'], + }, + ], + description: 'Configuration for third-party URL rewriting.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id', 'description'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts new file mode 100644 index 00000000..2035c6a5 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.usage', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/usage', + operationId: 'get-usage', +}; + +export const tool: Tool = { + name: 'get_accounts_usage', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + endDate: { + type: 'string', + description: + 'Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days.', + format: 'date', + }, + startDate: { + type: 'string', + description: + 'Specify a `startDate` in `YYYY-MM-DD` format. It should be before the `endDate`. The difference between `startDate` and `endDate` should be less than 90 days.', + format: 'date', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['endDate', 'startDate'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts new file mode 100644 index 00000000..2cc85df2 --- /dev/null +++ b/packages/mcp-server/src/tools/assets/list-assets.ts @@ -0,0 +1,94 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'assets', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files', + operationId: 'list-and-search-assets', +}; + +export const tool: Tool = { + name: 'list_assets', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileType: { + type: 'string', + description: + 'Filter results by file type.\n\n- `all` — include all file types \n- `image` — include only image files \n- `non-image` — include only non-image files (e.g., JS, CSS, video)', + enum: ['all', 'image', 'non-image'], + }, + limit: { + type: 'integer', + description: 'The maximum number of results to return in response.\n', + }, + path: { + type: 'string', + description: + 'Folder path if you want to limit the search within a specific folder. For example, `/sales-banner/` will only search in folder sales-banner.\n\nNote : If your use case involves searching within a folder as well as its subfolders, you can use `path` parameter in `searchQuery` with appropriate operator.\nCheckout [Supported parameters](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#supported-parameters) for more information.\n', + }, + searchQuery: { + type: 'string', + description: + 'Query string in a Lucene-like query language e.g. `createdAt > "7d"`.\n\nNote : When the searchQuery parameter is present, the following query parameters will have no effect on the result:\n\n1. `tags`\n2. `type`\n3. `name`\n\n[Learn more](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#advanced-search-queries) from examples.\n', + }, + skip: { + type: 'integer', + description: 'The number of results to skip before returning results.\n', + }, + sort: { + type: 'string', + description: 'Sort the results by one of the supported fields in ascending or descending order.', + enum: [ + 'ASC_NAME', + 'DESC_NAME', + 'ASC_CREATED', + 'DESC_CREATED', + 'ASC_UPDATED', + 'DESC_UPDATED', + 'ASC_HEIGHT', + 'DESC_HEIGHT', + 'ASC_WIDTH', + 'DESC_WIDTH', + 'ASC_SIZE', + 'DESC_SIZE', + 'ASC_RELEVANCE', + 'DESC_RELEVANCE', + ], + }, + type: { + type: 'string', + description: + 'Filter results by asset type.\n\n- `file` — returns only files \n- `file-version` — returns specific file versions \n- `folder` — returns only folders \n- `all` — returns both files and folders (excludes `file-version`)', + enum: ['file', 'file-version', 'folder', 'all'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts new file mode 100644 index 00000000..acac59c2 --- /dev/null +++ b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts @@ -0,0 +1,309 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'beta.v2.files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/api/v2/files/upload', + operationId: 'upload-file-v2', +}; + +export const tool: Tool = { + name: 'upload_v2_beta_files', + description: + 'The V2 API enhances security by verifying the entire payload using JWT. This API is in beta.\n\nImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', + inputSchema: { + type: 'object', + properties: { + file: { + type: 'string', + description: + 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', + }, + fileName: { + type: 'string', + description: 'The name with which the file has to be uploaded.\n', + }, + token: { + type: 'string', + description: + "This is the client-generated JSON Web Token (JWT). The ImageKit.io server uses it to authenticate and check that the upload request parameters have not been tampered with after the token has been generated. Learn how to create the token on the page below. This field is only required for authentication when uploading a file from the client side.\n\n\n**Note**: Sending a JWT that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new token.\n\n\n**⚠️Warning**: JWT must be generated on the server-side because it is generated using your account's private API key. This field is required for authentication when uploading a file from the client-side.\n", + }, + checks: { + type: 'string', + description: + 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file-v2#upload-api-checks).\n', + }, + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', + }, + customMetadata: { + type: 'object', + description: + 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + extensions: { + type: 'array', + description: + 'Array of extensions to be applied to the image. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + folder: { + type: 'string', + description: + "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. Using multiple `/` creates a nested folder.\n", + }, + isPrivateFile: { + type: 'boolean', + description: + 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', + }, + isPublished: { + type: 'boolean', + description: + 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', + }, + overwriteAITags: { + type: 'boolean', + description: + 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', + }, + overwriteCustomMetadata: { + type: 'boolean', + description: + 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', + }, + overwriteFile: { + type: 'boolean', + description: + 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', + }, + overwriteTags: { + type: 'boolean', + description: + 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', + }, + responseFields: { + type: 'array', + description: 'Array of response field keys to include in the API response body.\n', + items: { + type: 'string', + enum: [ + 'tags', + 'customCoordinates', + 'isPrivateFile', + 'embeddedMetadata', + 'isPublished', + 'customMetadata', + 'metadata', + ], + }, + }, + tags: { + type: 'array', + description: + 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', + items: { + type: 'string', + }, + }, + transformation: { + type: 'object', + description: + "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", + properties: { + post: { + type: 'array', + description: + 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Simple post-transformation', + properties: { + type: { + type: 'string', + description: 'Transformation type.', + enum: ['transformation'], + }, + value: { + type: 'string', + description: + 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', + }, + }, + required: ['type', 'value'], + }, + { + type: 'object', + title: 'Convert GIF to video', + properties: { + type: { + type: 'string', + description: 'Converts an animated GIF into an MP4.', + enum: ['gif-to-video'], + }, + value: { + type: 'string', + description: + 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Generate a thumbnail', + properties: { + type: { + type: 'string', + description: 'Generates a thumbnail image.', + enum: ['thumbnail'], + }, + value: { + type: 'string', + description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Adaptive Bitrate Streaming', + properties: { + protocol: { + type: 'string', + description: 'Streaming protocol to use (`hls` or `dash`).', + enum: ['hls', 'dash'], + }, + type: { + type: 'string', + description: 'Adaptive Bitrate Streaming (ABS) setup.', + enum: ['abs'], + }, + value: { + type: 'string', + description: + 'List of different representations you want to create separated by an underscore.\n', + }, + }, + required: ['protocol', 'type', 'value'], + }, + ], + }, + }, + pre: { + type: 'string', + description: + 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', + }, + }, + }, + useUniqueFileName: { + type: 'boolean', + description: + 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + required: ['file', 'fileName'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.beta.v2.files.upload(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts new file mode 100644 index 00000000..1bccd5a1 --- /dev/null +++ b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts @@ -0,0 +1,46 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'cache.invalidation', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/purge', + operationId: 'purge-cache', +}; + +export const tool: Tool = { + name: 'create_cache_invalidation', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + url: { + type: 'string', + description: 'The full URL of the file to be purged.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['url'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts new file mode 100644 index 00000000..4ef295b3 --- /dev/null +++ b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'cache.invalidation', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/purge/{requestId}', + operationId: 'purge-status', +}; + +export const tool: Tool = { + name: 'get_cache_invalidation', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + requestId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['requestId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { requestId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts new file mode 100644 index 00000000..a55ee9ae --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts @@ -0,0 +1,154 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/customMetadataFields', + operationId: 'create-new-field', +}; + +export const tool: Tool = { + name: 'create_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + label: { + type: 'string', + description: + 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI.', + }, + name: { + type: 'string', + description: + 'API name of the custom metadata field. This should be unique across all (including deleted) custom metadata fields.', + }, + schema: { + type: 'object', + properties: { + type: { + type: 'string', + description: 'Type of the custom metadata field.', + enum: ['Text', 'Textarea', 'Number', 'Date', 'Boolean', 'SingleSelect', 'MultiSelect'], + }, + defaultValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + { + type: 'array', + title: 'Mixed', + description: + 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + ], + description: + 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', + }, + isValueRequired: { + type: 'boolean', + description: + 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', + }, + maxLength: { + type: 'number', + description: + 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + maxValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + minLength: { + type: 'number', + description: + 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + minValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + selectOptions: { + type: 'array', + description: + 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + }, + required: ['type'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['label', 'name', 'schema'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts new file mode 100644 index 00000000..bb208216 --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/customMetadataFields/{id}', + operationId: 'delete-a-field', +}; + +export const tool: Tool = { + name: 'delete_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts new file mode 100644 index 00000000..b9d23289 --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/customMetadataFields', + operationId: 'list-all-fields', +}; + +export const tool: Tool = { + name: 'list_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n },\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + includeDeleted: { + type: 'boolean', + description: 'Set it to `true` to include deleted field objects in the API response.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts new file mode 100644 index 00000000..ae7291fb --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts @@ -0,0 +1,150 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/v1/customMetadataFields/{id}', + operationId: 'update-existing-field', +}; + +export const tool: Tool = { + name: 'update_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API updates the label or schema of an existing custom metadata field.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + label: { + type: 'string', + description: + 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI. This parameter is required if `schema` is not provided.', + }, + schema: { + type: 'object', + description: + 'An object that describes the rules for the custom metadata key. This parameter is required if `label` is not provided. Note: `type` cannot be updated and will be ignored if sent with the `schema`. The schema will be validated as per the existing `type`.\n', + properties: { + defaultValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + { + type: 'array', + title: 'Mixed', + description: + 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + ], + description: + 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', + }, + isValueRequired: { + type: 'boolean', + description: + 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', + }, + maxLength: { + type: 'number', + description: + 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + maxValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + minLength: { + type: 'number', + description: + 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + minValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + selectOptions: { + type: 'array', + description: + 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts new file mode 100644 index 00000000..f6a051c2 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/addTags', + operationId: 'add-tags-bulk', +}; + +export const tool: Tool = { + name: 'add_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds to which you want to add tags.\n', + items: { + type: 'string', + }, + }, + tags: { + type: 'array', + description: 'An array of tags that you want to add to the files.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds', 'tags'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts new file mode 100644 index 00000000..b41409b3 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts @@ -0,0 +1,49 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/batch/deleteByFileIds', + operationId: 'delete-multiple-files', +}; + +export const tool: Tool = { + name: 'delete_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds which you want to delete.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts new file mode 100644 index 00000000..f54f024a --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/removeAITags', + operationId: 'remove-ai-tags-bulk', +}; + +export const tool: Tool = { + name: 'remove_ai_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + AITags: { + type: 'array', + description: 'An array of AITags that you want to remove from the files.\n', + items: { + type: 'string', + }, + }, + fileIds: { + type: 'array', + description: 'An array of fileIds from which you want to remove AITags.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['AITags', 'fileIds'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts new file mode 100644 index 00000000..d51184d2 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/removeTags', + operationId: 'remove-tags-bulk', +}; + +export const tool: Tool = { + name: 'remove_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds from which you want to remove tags.\n', + items: { + type: 'string', + }, + }, + tags: { + type: 'array', + description: 'An array of tags that you want to remove from the files.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds', 'tags'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts new file mode 100644 index 00000000..d7002a58 --- /dev/null +++ b/packages/mcp-server/src/tools/files/copy-files.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/copy', + operationId: 'copy-file', +}; + +export const tool: Tool = { + name: 'copy_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the folder you want to copy the above file into.\n', + }, + sourceFilePath: { + type: 'string', + description: 'The full path of the file you want to copy.\n', + }, + includeFileVersions: { + type: 'boolean', + description: + 'Option to copy all versions of a file. By default, only the current version of the file is copied. When set to true, all versions of the file will be copied. Default value - `false`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFilePath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts new file mode 100644 index 00000000..5cc7e0a8 --- /dev/null +++ b/packages/mcp-server/src/tools/files/delete-files.ts @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/files/{fileId}', + operationId: 'delete-file', +}; + +export const tool: Tool = { + name: 'delete_files', + description: + 'This API deletes the file and all its file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n', + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + }, + required: ['fileId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, ...body } = args as any; + const response = await client.files.delete(fileId).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts new file mode 100644 index 00000000..d7e69593 --- /dev/null +++ b/packages/mcp-server/src/tools/files/get-files.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/details', + operationId: 'get-file-details', +}; + +export const tool: Tool = { + name: 'get_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes about the current version of the file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts new file mode 100644 index 00000000..f6ce3f07 --- /dev/null +++ b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.metadata', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/metadata', + operationId: 'get-uploaded-file-metadata', +}; + +export const tool: Tool = { + name: 'get_files_metadata', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API.\n\nYou can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts new file mode 100644 index 00000000..b3758ad9 --- /dev/null +++ b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.metadata', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/metadata', + operationId: 'get-metadata-from-url', +}; + +export const tool: Tool = { + name: 'get_from_url_files_metadata', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + url: { + type: 'string', + description: 'Should be a valid file URL. It should be accessible using your ImageKit.io account.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['url'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts new file mode 100644 index 00000000..a4b07ec4 --- /dev/null +++ b/packages/mcp-server/src/tools/files/move-files.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/move', + operationId: 'move-file', +}; + +export const tool: Tool = { + name: 'move_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the folder you want to move the above file into.\n', + }, + sourceFilePath: { + type: 'string', + description: 'The full path of the file you want to move.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFilePath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts new file mode 100644 index 00000000..16d682c6 --- /dev/null +++ b/packages/mcp-server/src/tools/files/rename-files.ts @@ -0,0 +1,58 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/files/rename', + operationId: 'rename-file', +}; + +export const tool: Tool = { + name: 'rename_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + filePath: { + type: 'string', + description: 'The full path of the file you want to rename.\n', + }, + newFileName: { + type: 'string', + description: + 'The new name of the file. A filename can contain:\n\nAlphanumeric Characters: `a-z`, `A-Z`, `0-9` (including Unicode letters, marks, and numerals in other languages).\nSpecial Characters: `.`, `_`, and `-`.\n\nAny other character, including space, will be replaced by `_`.\n', + }, + purgeCache: { + type: 'boolean', + description: + "Option to purge cache for the old file and its versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove cached content of old file and its versions. This purge request is counted against your monthly purge quota.\n\nNote: If the old file were accessible at `https://ik.imagekit.io/demo/old-filename.jpg`, a purge cache request would be issued against `https://ik.imagekit.io/demo/old-filename.jpg*` (with a wildcard at the end). It will remove the file and its versions' URLs and any transformations made using query parameters on this file or its versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\n\n\nDefault value - `false`\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['filePath', 'newFileName'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/update-files.ts b/packages/mcp-server/src/tools/files/update-files.ts new file mode 100644 index 00000000..bcd87a73 --- /dev/null +++ b/packages/mcp-server/src/tools/files/update-files.ts @@ -0,0 +1,192 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/v1/files/{fileId}/details', + operationId: 'update-file-details', +}; + +export const tool: Tool = { + name: 'update_files', + description: + 'This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API.\n', + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + update: { + anyOf: [ + { + type: 'object', + title: 'Update file details', + properties: { + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image in the format `x,y,width,height` e.g. `10,10,100,100`. Send `null` to unset this value.\n', + }, + customMetadata: { + type: 'object', + description: + 'A key-value data to be associated with the asset. To unset a key, send `null` value for that key. Before setting any custom metadata on an asset you have to create the field using custom metadata fields API.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + extensions: { + type: 'array', + description: + 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + removeAITags: { + anyOf: [ + { + type: 'array', + items: { + type: 'string', + }, + }, + { + type: 'string', + enum: ['all'], + }, + ], + description: + 'An array of AITags associated with the file that you want to remove, e.g. `["car", "vehicle", "motorsports"]`. \n\nIf you want to remove all AITags associated with the file, send a string - "all".\n\nNote: The remove operation for `AITags` executes before any of the `extensions` are processed.\n', + }, + tags: { + type: 'array', + description: + 'An array of tags associated with the file, such as `["tag1", "tag2"]`. Send `null` to unset all tags associated with the file.\n', + items: { + type: 'string', + }, + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + }, + { + type: 'object', + title: 'Change publication status', + properties: { + publish: { + type: 'object', + description: 'Configure the publication status of a file and its versions.\n', + properties: { + isPublished: { + type: 'boolean', + description: 'Set to `true` to publish the file. Set to `false` to unpublish the file.\n', + }, + includeFileVersions: { + type: 'boolean', + description: + 'Set to `true` to publish/unpublish all versions of the file. Set to `false` to publish/unpublish only the current version of the file.\n', + }, + }, + required: ['isPublished'], + }, + }, + }, + ], + }, + }, + required: ['fileId'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, ...body } = args as any; + return asTextContentResult(await client.files.update(fileId, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts new file mode 100644 index 00000000..6479106d --- /dev/null +++ b/packages/mcp-server/src/tools/files/upload-files.ts @@ -0,0 +1,325 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/api/v1/files/upload', + operationId: 'upload-file', +}; + +export const tool: Tool = { + name: 'upload_files', + description: + 'ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload.\n\nThe [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', + inputSchema: { + type: 'object', + properties: { + file: { + type: 'string', + description: + 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', + }, + fileName: { + type: 'string', + description: + 'The name with which the file has to be uploaded.\nThe file name can contain:\n\n - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`.\n - Special Characters: `.`, `-`\n\nAny other character including space will be replaced by `_`\n', + }, + token: { + type: 'string', + description: + 'A unique value that the ImageKit.io server will use to recognize and prevent subsequent retries for the same request. We suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. This field is only required for authentication when uploading a file from the client side.\n\n**Note**: Sending a value that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new value for this field.\n', + }, + checks: { + type: 'string', + description: + 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks).\n', + }, + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', + }, + customMetadata: { + type: 'object', + description: + 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + expire: { + type: 'integer', + description: + 'The time until your signature is valid. It must be a [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into the future. It should be in seconds. This field is only required for authentication when uploading a file from the client side.\n', + }, + extensions: { + type: 'array', + description: + 'Array of extensions to be applied to the image. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + folder: { + type: 'string', + description: + "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created.\n\nThe folder name can contain:\n\n - Alphanumeric Characters: `a-z` , `A-Z` , `0-9`\n - Special Characters: `/` , `_` , `-`\n\nUsing multiple `/` creates a nested folder.\n", + }, + isPrivateFile: { + type: 'boolean', + description: + 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', + }, + isPublished: { + type: 'boolean', + description: + 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', + }, + overwriteAITags: { + type: 'boolean', + description: + 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', + }, + overwriteCustomMetadata: { + type: 'boolean', + description: + 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', + }, + overwriteFile: { + type: 'boolean', + description: + 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', + }, + overwriteTags: { + type: 'boolean', + description: + 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', + }, + publicKey: { + type: 'string', + description: + 'Your ImageKit.io public key. This field is only required for authentication when uploading a file from the client side.\n', + }, + responseFields: { + type: 'array', + description: 'Array of response field keys to include in the API response body.\n', + items: { + type: 'string', + enum: [ + 'tags', + 'customCoordinates', + 'isPrivateFile', + 'embeddedMetadata', + 'isPublished', + 'customMetadata', + 'metadata', + ], + }, + }, + signature: { + type: 'string', + description: + 'HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a key. Learn how to create a signature on the page below. This should be in lowercase.\n\nSignature must be calculated on the server-side. This field is only required for authentication when uploading a file from the client side.\n', + }, + tags: { + type: 'array', + description: + 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', + items: { + type: 'string', + }, + }, + transformation: { + type: 'object', + description: + "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", + properties: { + post: { + type: 'array', + description: + 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Simple post-transformation', + properties: { + type: { + type: 'string', + description: 'Transformation type.', + enum: ['transformation'], + }, + value: { + type: 'string', + description: + 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', + }, + }, + required: ['type', 'value'], + }, + { + type: 'object', + title: 'Convert GIF to video', + properties: { + type: { + type: 'string', + description: 'Converts an animated GIF into an MP4.', + enum: ['gif-to-video'], + }, + value: { + type: 'string', + description: + 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Generate a thumbnail', + properties: { + type: { + type: 'string', + description: 'Generates a thumbnail image.', + enum: ['thumbnail'], + }, + value: { + type: 'string', + description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Adaptive Bitrate Streaming', + properties: { + protocol: { + type: 'string', + description: 'Streaming protocol to use (`hls` or `dash`).', + enum: ['hls', 'dash'], + }, + type: { + type: 'string', + description: 'Adaptive Bitrate Streaming (ABS) setup.', + enum: ['abs'], + }, + value: { + type: 'string', + description: + 'List of different representations you want to create separated by an underscore.\n', + }, + }, + required: ['protocol', 'type', 'value'], + }, + ], + }, + }, + pre: { + type: 'string', + description: + 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', + }, + }, + }, + useUniqueFileName: { + type: 'boolean', + description: + 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + required: ['file', 'fileName'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.files.upload(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts new file mode 100644 index 00000000..5688f6ad --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/files/{fileId}/versions/{versionId}', + operationId: 'delete-file-version', +}; + +export const tool: Tool = { + name: 'delete_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts new file mode 100644 index 00000000..ecb444c8 --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/versions/{versionId}', + operationId: 'get-file-version-details', +}; + +export const tool: Tool = { + name: 'get_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes of a file version.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.get(versionId, body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts new file mode 100644 index 00000000..94765bdc --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/versions', + operationId: 'list-file-versions', +}; + +export const tool: Tool = { + name: 'list_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts new file mode 100644 index 00000000..177dfc8f --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/files/{fileId}/versions/{versionId}/restore', + operationId: 'restore-file-version', +}; + +export const tool: Tool = { + name: 'restore_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API restores a file version as the current file version.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts new file mode 100644 index 00000000..6a748982 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/copy-folders.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/copyFolder', + operationId: 'copy-folder', +}; + +export const tool: Tool = { + name: 'copy_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the destination folder where you want to copy the source folder into.\n', + }, + sourceFolderPath: { + type: 'string', + description: 'The full path to the source folder you want to copy.\n', + }, + includeVersions: { + type: 'boolean', + description: + 'Option to copy all versions of files that are nested inside the selected folder. By default, only the current version of each file will be copied. When set to true, all versions of each file will be copied. Default value - `false`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts new file mode 100644 index 00000000..01823636 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/create-folders.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/folder', + operationId: 'create-folder', +}; + +export const tool: Tool = { + name: 'create_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderName: { + type: 'string', + description: + 'The folder will be created with this name. \n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) will be replaced by an underscore i.e. `_`.\n', + }, + parentFolderPath: { + type: 'string', + description: + "The folder where the new folder should be created, for root use `/` else the path e.g. `containing/folder/`.\n\nNote: If any folder(s) is not present in the parentFolderPath parameter, it will be automatically created. For example, if you pass `/product/images/summer`, then `product`, `images`, and `summer` folders will be created if they don't already exist.\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderName', 'parentFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts new file mode 100644 index 00000000..ddd2fdd7 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/delete-folders.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/folder', + operationId: 'delete-folder', +}; + +export const tool: Tool = { + name: 'delete_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderPath: { + type: 'string', + description: 'Full path to the folder you want to delete. For example `/folder/to/delete/`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderPath'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts new file mode 100644 index 00000000..51a6af24 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders.job', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/bulkJobs/{jobId}', + operationId: 'bulk-job-status', +}; + +export const tool: Tool = { + name: 'get_folders_job', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jobId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['jobId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jobId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts new file mode 100644 index 00000000..0d035986 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/move-folders.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/moveFolder', + operationId: 'move-folder', +}; + +export const tool: Tool = { + name: 'move_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the destination folder where you want to move the source folder into.\n', + }, + sourceFolderPath: { + type: 'string', + description: 'The full path to the source folder you want to move.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts new file mode 100644 index 00000000..3640cb2e --- /dev/null +++ b/packages/mcp-server/src/tools/folders/rename-folders.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; +import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/renameFolder', + operationId: 'rename-folder', +}; + +export const tool: Tool = { + name: 'rename_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderPath: { + type: 'string', + description: 'The full path to the folder you want to rename.\n', + }, + newFolderName: { + type: 'string', + description: + 'The new name for the folder.\n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) and `-` will be replaced by an underscore i.e. `_`.\n', + }, + purgeCache: { + type: 'boolean', + description: + "Option to purge cache for the old nested files and their versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove the cached content of the old nested files and their versions. There will only be one purge request for all the nested files, which will be counted against your monthly purge quota.\n\nNote: A purge cache request will be issued against `https://ik.imagekit.io/old/folder/path*` (with a wildcard at the end). This will remove all nested files, their versions' URLs, and any transformations made using query parameters on these files or their versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\nDefault value - `false`\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderPath', 'newFolderName'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts new file mode 100644 index 00000000..ba7083d7 --- /dev/null +++ b/packages/mcp-server/src/tools/index.ts @@ -0,0 +1,153 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, Endpoint, HandlerFunction } from './types'; + +export { Metadata, Endpoint, HandlerFunction }; + +import create_custom_metadata_fields from './custom-metadata-fields/create-custom-metadata-fields'; +import update_custom_metadata_fields from './custom-metadata-fields/update-custom-metadata-fields'; +import list_custom_metadata_fields from './custom-metadata-fields/list-custom-metadata-fields'; +import delete_custom_metadata_fields from './custom-metadata-fields/delete-custom-metadata-fields'; +import update_files from './files/update-files'; +import delete_files from './files/delete-files'; +import copy_files from './files/copy-files'; +import get_files from './files/get-files'; +import move_files from './files/move-files'; +import rename_files from './files/rename-files'; +import upload_files from './files/upload-files'; +import delete_files_bulk from './files/bulk/delete-files-bulk'; +import add_tags_files_bulk from './files/bulk/add-tags-files-bulk'; +import remove_ai_tags_files_bulk from './files/bulk/remove-ai-tags-files-bulk'; +import remove_tags_files_bulk from './files/bulk/remove-tags-files-bulk'; +import list_files_versions from './files/versions/list-files-versions'; +import delete_files_versions from './files/versions/delete-files-versions'; +import get_files_versions from './files/versions/get-files-versions'; +import restore_files_versions from './files/versions/restore-files-versions'; +import get_files_metadata from './files/metadata/get-files-metadata'; +import get_from_url_files_metadata from './files/metadata/get-from-url-files-metadata'; +import list_assets from './assets/list-assets'; +import create_cache_invalidation from './cache/invalidation/create-cache-invalidation'; +import get_cache_invalidation from './cache/invalidation/get-cache-invalidation'; +import create_folders from './folders/create-folders'; +import delete_folders from './folders/delete-folders'; +import copy_folders from './folders/copy-folders'; +import move_folders from './folders/move-folders'; +import rename_folders from './folders/rename-folders'; +import get_folders_job from './folders/job/get-folders-job'; +import get_accounts_usage from './accounts/usage/get-accounts-usage'; +import create_accounts_origins from './accounts/origins/create-accounts-origins'; +import update_accounts_origins from './accounts/origins/update-accounts-origins'; +import list_accounts_origins from './accounts/origins/list-accounts-origins'; +import delete_accounts_origins from './accounts/origins/delete-accounts-origins'; +import get_accounts_origins from './accounts/origins/get-accounts-origins'; +import create_accounts_url_endpoints from './accounts/url-endpoints/create-accounts-url-endpoints'; +import update_accounts_url_endpoints from './accounts/url-endpoints/update-accounts-url-endpoints'; +import list_accounts_url_endpoints from './accounts/url-endpoints/list-accounts-url-endpoints'; +import delete_accounts_url_endpoints from './accounts/url-endpoints/delete-accounts-url-endpoints'; +import get_accounts_url_endpoints from './accounts/url-endpoints/get-accounts-url-endpoints'; +import upload_v2_beta_files from './beta/v2/files/upload-v2-beta-files'; + +export const endpoints: Endpoint[] = []; + +function addEndpoint(endpoint: Endpoint) { + endpoints.push(endpoint); +} + +addEndpoint(create_custom_metadata_fields); +addEndpoint(update_custom_metadata_fields); +addEndpoint(list_custom_metadata_fields); +addEndpoint(delete_custom_metadata_fields); +addEndpoint(update_files); +addEndpoint(delete_files); +addEndpoint(copy_files); +addEndpoint(get_files); +addEndpoint(move_files); +addEndpoint(rename_files); +addEndpoint(upload_files); +addEndpoint(delete_files_bulk); +addEndpoint(add_tags_files_bulk); +addEndpoint(remove_ai_tags_files_bulk); +addEndpoint(remove_tags_files_bulk); +addEndpoint(list_files_versions); +addEndpoint(delete_files_versions); +addEndpoint(get_files_versions); +addEndpoint(restore_files_versions); +addEndpoint(get_files_metadata); +addEndpoint(get_from_url_files_metadata); +addEndpoint(list_assets); +addEndpoint(create_cache_invalidation); +addEndpoint(get_cache_invalidation); +addEndpoint(create_folders); +addEndpoint(delete_folders); +addEndpoint(copy_folders); +addEndpoint(move_folders); +addEndpoint(rename_folders); +addEndpoint(get_folders_job); +addEndpoint(get_accounts_usage); +addEndpoint(create_accounts_origins); +addEndpoint(update_accounts_origins); +addEndpoint(list_accounts_origins); +addEndpoint(delete_accounts_origins); +addEndpoint(get_accounts_origins); +addEndpoint(create_accounts_url_endpoints); +addEndpoint(update_accounts_url_endpoints); +addEndpoint(list_accounts_url_endpoints); +addEndpoint(delete_accounts_url_endpoints); +addEndpoint(get_accounts_url_endpoints); +addEndpoint(upload_v2_beta_files); + +export type Filter = { + type: 'resource' | 'operation' | 'tag' | 'tool'; + op: 'include' | 'exclude'; + value: string; +}; + +export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { + const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); + const unmatchedFilters = new Set(filters); + + const filtered = endpoints.filter((endpoint: Endpoint) => { + let included = false || allExcludes; + + for (const filter of filters) { + if (match(filter, endpoint)) { + unmatchedFilters.delete(filter); + included = filter.op === 'include'; + } + } + + return included; + }); + + // Check if any filters didn't match + const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); + if (unmatched.length > 0) { + throw new Error( + `The following filters did not match any endpoints: ${unmatched + .map((f) => `${f.type}=${f.value}`) + .join(', ')}`, + ); + } + + return filtered; +} + +function match({ type, value }: Filter, endpoint: Endpoint): boolean { + switch (type) { + case 'resource': { + const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; + const regex = new RegExp(regexStr); + return regex.test(normalizeResource(endpoint.metadata.resource)); + } + case 'operation': + return endpoint.metadata.operation === value; + case 'tag': + return endpoint.metadata.tags.includes(value); + case 'tool': + return endpoint.tool.name === value; + } +} + +function normalizeResource(resource: string): string { + return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); +} diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts new file mode 100644 index 00000000..8106d499 --- /dev/null +++ b/packages/mcp-server/src/tools/types.ts @@ -0,0 +1,103 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +type TextContentBlock = { + type: 'text'; + text: string; +}; + +type ImageContentBlock = { + type: 'image'; + data: string; + mimeType: string; +}; + +type AudioContentBlock = { + type: 'audio'; + data: string; + mimeType: string; +}; + +type ResourceContentBlock = { + type: 'resource'; + resource: + | { + uri: string; + mimeType: string; + text: string; + } + | { + uri: string; + mimeType: string; + blob: string; + }; +}; + +export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock; + +export type ToolCallResult = { + content: ContentBlock[]; + isError?: boolean; +}; + +export type HandlerFunction = ( + client: ImageKit, + args: Record | undefined, +) => Promise; + +export function asTextContentResult(result: unknown): ToolCallResult { + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +} + +export async function asBinaryContentResult(response: Response): Promise { + const blob = await response.blob(); + const mimeType = blob.type; + const data = Buffer.from(await blob.arrayBuffer()).toString('base64'); + if (mimeType.startsWith('image/')) { + return { + content: [{ type: 'image', mimeType, data }], + }; + } else if (mimeType.startsWith('audio/')) { + return { + content: [{ type: 'audio', mimeType, data }], + }; + } else { + return { + content: [ + { + type: 'resource', + resource: { + // We must give a URI, even though this isn't actually an MCP resource. + uri: 'resource://tool-response', + mimeType, + blob: data, + }, + }, + ], + }; + } +} + +export type Metadata = { + resource: string; + operation: 'read' | 'write'; + tags: string[]; + httpMethod?: string; + httpPath?: string; + operationId?: string; +}; + +export type Endpoint = { + metadata: Metadata; + tool: Tool; + handler: HandlerFunction; +}; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts new file mode 100644 index 00000000..d6272f6c --- /dev/null +++ b/packages/mcp-server/tests/compat.test.ts @@ -0,0 +1,1166 @@ +import { + truncateToolNames, + removeTopLevelUnions, + removeAnyOf, + inlineRefs, + applyCompatibilityTransformations, + removeFormats, + findUsedDefs, +} from '../src/compat'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { JSONSchema } from '../src/compat'; +import { Endpoint } from '../src/tools'; + +describe('truncateToolNames', () => { + it('should return original names when maxLength is 0 or negative', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 0)).toEqual(new Map()); + expect(truncateToolNames(names, -1)).toEqual(new Map()); + }); + + it('should return original names when all names are shorter than maxLength', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 10)).toEqual(new Map()); + }); + + it('should truncate names longer than maxLength', () => { + const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; + expect(truncateToolNames(names, 10)).toEqual( + new Map([ + ['very-long-tool-name', 'very-long-'], + ['another-long-tool-name', 'another-lo'], + ]), + ); + }); + + it('should handle duplicate truncated names by appending numbers', () => { + const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; + expect(truncateToolNames(names, 8)).toEqual( + new Map([ + ['tool-name-a', 'tool-na1'], + ['tool-name-b', 'tool-na2'], + ['tool-name-c', 'tool-na3'], + ]), + ); + }); +}); + +describe('removeTopLevelUnions', () => { + const createTestTool = (overrides = {}): Tool => ({ + name: 'test-tool', + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + it('should return the original tool if it has no anyOf at the top level', () => { + const tool = createTestTool({ + inputSchema: { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + }, + }); + + expect(removeTopLevelUnions(tool)).toEqual([tool]); + }); + + it('should split a tool with top-level anyOf into multiple tools', () => { + const tool = createTestTool({ + name: 'union-tool', + description: 'A tool with unions', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + description: 'Its the first variant', + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'union-tool_first_variant', + description: 'Its the first variant', + inputSchema: { + type: 'object', + title: 'first variant', + description: 'Its the first variant', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + }, + { + name: 'union-tool_second_variant', + description: 'A tool with unions', + inputSchema: { + type: 'object', + title: 'second variant', + description: 'A tool with unions', + properties: { + common: { type: 'string' }, + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + }, + ]); + }); + + it('should handle $defs and only include those used by the variant', () => { + const tool = createTestTool({ + name: 'defs-tool', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + def2: { type: 'number', minimum: 0 }, + unused: { type: 'boolean' }, + }, + anyOf: [ + { + properties: { + email: { $ref: '#/$defs/def1' }, + }, + }, + { + properties: { + count: { $ref: '#/$defs/def2' }, + }, + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'defs-tool_variant1', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + email: { $ref: '#/$defs/def1' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + }, + }, + }, + { + name: 'defs-tool_variant2', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + count: { $ref: '#/$defs/def2' }, + }, + $defs: { + def2: { type: 'number', minimum: 0 }, + }, + }, + }, + ]); + }); +}); + +describe('removeAnyOf', () => { + it('should return original schema if it has no anyOf', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { type: 'number' }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(schema); + }); + + it('should remove anyOf field and use the first variant', () => { + const schema = { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }; + + const expected = { + type: 'object', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should recursively remove anyOf fields from nested properties', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + }, + anyOf: [ + { + properties: { + option1: { type: 'boolean' }, + }, + }, + { + properties: { + option2: { type: 'array' }, + }, + }, + ], + }, + }, + }; + + const expected = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + option1: { type: 'boolean' }, + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should handle arrays', () => { + const schema = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); +}); + +describe('findUsedDefs', () => { + it('should handle circular references without stack overflow', () => { + const defs = { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/person' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('person'); + }).not.toThrow(); + }); + + it('should handle indirect circular references without stack overflow', () => { + const defs = { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Indirect circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + root: { $ref: '#/$defs/node' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('node'); + expect(result).toHaveProperty('childNode'); + }).not.toThrow(); + }); + + it('should find all used definitions in non-circular schemas', () => { + const defs = { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { $ref: '#/$defs/address' }, + }, + }, + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + unused: { + type: 'object', + properties: { + data: { type: 'string' }, + }, + }, + }; + + const schema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/user' }, + }, + }; + + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('user'); + expect(result).toHaveProperty('address'); + expect(result).not.toHaveProperty('unused'); + }); +}); + +describe('inlineRefs', () => { + it('should return the original schema if it does not contain $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + }; + + expect(inlineRefs(schema)).toEqual(schema); + }); + + it('should inline simple $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should inline nested $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + order: { $ref: '#/$defs/order' }, + }, + $defs: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { type: 'array', items: { $ref: '#/$defs/item' } }, + }, + }, + item: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle circular references by removing the circular part', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/person' }, + }, + $defs: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + // friend property is removed to break the circular reference + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle indirect circular references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + node: { $ref: '#/$defs/node' }, + }, + $defs: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Circular reference through childNode + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { + type: 'object', + properties: { + value: { type: 'string' }, + // parent property is removed to break the circular reference + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should preserve other properties when inlining references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + address: { $ref: '#/$defs/address', description: 'User address' }, + }, + $defs: { + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + address: { + type: 'object', + description: 'User address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); +}); + +describe('removeFormats', () => { + it('should return original schema if formats capability is true', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + expect(removeFormats(schema, true)).toEqual(schema); + }); + + it('should move format to description when formats capability is false', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + email: { type: 'string', description: 'An email field (format: "email")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle properties without description', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', format: 'date' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: '(format: "date")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle nested properties', () => { + const schema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle arrays of objects', () => { + const schema = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date', format: 'date' }, + end: { type: 'string', description: 'End date', format: 'date' }, + }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date (format: "date")' }, + end: { type: 'string', description: 'End date (format: "date")' }, + }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle schemas with $defs', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field', + format: 'date-time', + }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field (format: "date-time")', + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); +}); + +describe('applyCompatibilityTransformations', () => { + const createTestTool = (name: string, overrides = {}): Tool => ({ + name, + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + const createTestEndpoint = (tool: Tool): Endpoint => ({ + tool, + handler: jest.fn(), + metadata: { + resource: 'test', + operation: 'read' as const, + tags: [], + }, + }); + + it('should not modify endpoints when all capabilities are enabled', () => { + const tool = createTestTool('test-tool'); + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed).toEqual(endpoints); + }); + + it('should split tools with top-level unions when topLevelUnions is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); + expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); + }); + + it('should handle variants without titles in removeTopLevelUnions', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + }, + { + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); + expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); + }); + + it('should truncate tool names when toolNameLength is set', () => { + const tools = [ + createTestTool('very-long-tool-name-that-exceeds-limit'), + createTestTool('another-long-tool-name-to-truncate'), + createTestTool('short-name'), + ]; + + const endpoints = tools.map(createTestEndpoint); + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); + expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); + expect(transformed[2]!.tool.name).toBe('short-name'); + }); + + it('should inline refs when refs capability is disabled', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + expect(schema.$defs).toBeUndefined(); + + if (schema.properties) { + expect(schema.properties['user']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }); + } + }); + + it('should preserve external refs when inlining', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + internal: { $ref: '#/$defs/internal' }, + external: { $ref: 'https://example.com/schemas/external.json' }, + }, + $defs: { + internal: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties) { + expect(schema.properties['internal']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + }, + }); + expect(schema.properties['external']).toEqual({ + $ref: 'https://example.com/schemas/external.json', + }); + } + }); + + it('should remove anyOf fields when unions capability is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + field: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['field']) { + const field = schema.properties['field']; + expect(field.anyOf).toBeUndefined(); + expect(field.type).toBe('string'); + } + }); + + it('should correctly combine topLevelUnions and toolNameLength transformations', () => { + const tool = createTestTool('very-long-union-tool-name', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + + // Both names should be truncated because they exceed 20 characters + expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); + expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); + }); + + it('should correctly combine refs and unions transformations', () => { + const tool = createTestTool('complex-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + preference: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + // Refs should be inlined + expect(schema.$defs).toBeUndefined(); + + // Safely access nested properties + if (schema.properties && schema.properties['user']) { + const user = schema.properties['user']; + // User should be inlined + expect(user.type).toBe('object'); + + // AnyOf in the inlined user.preference should be removed + if (user.properties && user.properties['preference']) { + const preference = user.properties['preference']; + expect(preference.anyOf).toBeUndefined(); + expect(preference.type).toBe('string'); + } + } + }); + + it('should handle formats capability being false', () => { + const tool = createTestTool('format-tool', { + inputSchema: { + type: 'object', + properties: { + date: { type: 'string', description: 'A date', format: 'date' }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: false, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['date']) { + const dateField = schema.properties['date']; + expect(dateField['format']).toBeUndefined(); + expect(dateField['description']).toBe('A date (format: "date")'); + } + }); +}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts new file mode 100644 index 00000000..08963af8 --- /dev/null +++ b/packages/mcp-server/tests/dynamic-tools.test.ts @@ -0,0 +1,185 @@ +import { dynamicTools } from '../src/dynamic-tools'; +import { Endpoint } from '../src/tools'; + +describe('dynamicTools', () => { + const fakeClient = {} as any; + + const endpoints: Endpoint[] = [ + makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), + makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), + makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), + makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), + ]; + + const tools = dynamicTools(endpoints); + + const toolsMap = { + list_api_endpoints: toolOrError('list_api_endpoints'), + get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), + invoke_api_endpoint: toolOrError('invoke_api_endpoint'), + }; + + describe('list_api_endpoints', () => { + it('should return all endpoints when no search query is provided', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(endpoints.length); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); + }); + + it('should filter endpoints by name', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + + it('should filter endpoints by resource', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); + }); + + it('should filter endpoints by tag', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); + }); + + it('should be case insensitive in search', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.length).toBe(2); + result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { + expect( + tool.name.toLowerCase().includes('admin') || + tool.resource.toLowerCase().includes('admin') || + tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), + ).toBeTruthy(); + }); + }); + + it('should filter endpoints by description', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'Test endpoint for user_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); + }); + + it('should filter endpoints by partial description match', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'endpoint for user', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + }); + + describe('get_api_endpoint_schema', () => { + it('should return schema for existing endpoint', async () => { + const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { + endpoint: 'test_read_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result).toEqual(endpoints[0]?.tool); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), + ).rejects.toThrow('Endpoint non_existent_endpoint not found'); + }); + + it('should throw error when no endpoint provided', async () => { + await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + }); + + describe('invoke_api_endpoint', () => { + it('should successfully invoke endpoint with valid arguments', async () => { + const mockHandler = endpoints[0]?.handler as jest.Mock; + mockHandler.mockClear(); + + await toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { testParam: 'test value' }, + }); + + expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'non_existent_endpoint', + args: { testParam: 'test value' }, + }), + ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); + }); + + it('should throw error when no arguments provided', async () => { + await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + + it('should throw error for invalid argument schema', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { wrongParam: 'test value' }, // Missing required testParam + }), + ).rejects.toThrow(/Invalid arguments for endpoint/); + }); + }); + + function toolOrError(name: string) { + const tool = tools.find((tool) => tool.tool.name === name); + if (!tool) throw new Error(`Tool ${name} not found`); + return tool; + } +}); + +function makeEndpoint( + name: string, + resource: string, + operation: 'read' | 'write', + tags: string[] = [], +): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { + name, + description: `Test endpoint for ${name}`, + inputSchema: { + type: 'object', + properties: { + testParam: { type: 'string' }, + }, + required: ['testParam'], + }, + }, + handler: jest.fn().mockResolvedValue({ success: true }), + }; +} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts new file mode 100644 index 00000000..a8a5b81a --- /dev/null +++ b/packages/mcp-server/tests/options.test.ts @@ -0,0 +1,518 @@ +import { parseCLIOptions, parseQueryOptions } from '../src/options'; +import { Filter } from '../src/tools'; +import { parseEmbeddedJSON } from '../src/compat'; + +// Mock process.argv +const mockArgv = (args: string[]) => { + const originalArgv = process.argv; + process.argv = ['node', 'test.js', ...args]; + return () => { + process.argv = originalArgv; + }; +}; + +describe('parseCLIOptions', () => { + it('should parse basic filter options', () => { + const cleanup = mockArgv([ + '--tool=test-tool', + '--resource=test-resource', + '--operation=read', + '--tag=test-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + { type: 'operation', op: 'include', value: 'read' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + expect(result.list).toBe(false); + + cleanup(); + }); + + it('should parse exclusion filters', () => { + const cleanup = mockArgv([ + '--no-tool=exclude-tool', + '--no-resource=exclude-resource', + '--no-operation=write', + '--no-tag=exclude-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + cleanup(); + }); + + it('should parse client presets', () => { + const cleanup = mockArgv(['--client=openai-agents']); + + const result = parseCLIOptions(); + + expect(result.client).toEqual('openai-agents'); + + cleanup(); + }); + + it('should parse individual capabilities', () => { + const cleanup = mockArgv([ + '--capability=top-level-unions', + '--capability=valid-json', + '--capability=refs', + '--capability=unions', + '--capability=tool-name-length=40', + ]); + + const result = parseCLIOptions(); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + toolNameLength: 40, + }); + + cleanup(); + }); + + it('should handle list option', () => { + const cleanup = mockArgv(['--list']); + + const result = parseCLIOptions(); + + expect(result.list).toBe(true); + + cleanup(); + }); + + it('should handle multiple filters of the same type', () => { + const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + cleanup(); + }); + + it('should handle comma-separated values in array options', () => { + const cleanup = mockArgv([ + '--tool=tool1,tool2', + '--resource=res1,res2', + '--capability=top-level-unions,valid-json,unions', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + unions: true, + }); + + cleanup(); + }); + + it('should handle invalid tool-name-length format', () => { + const cleanup = mockArgv(['--capability=tool-name-length=invalid']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); + + it('should handle unknown capability', () => { + const cleanup = mockArgv(['--capability=unknown-capability']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); +}); + +describe('parseQueryOptions', () => { + const defaultOptions = { + client: undefined, + includeDynamicTools: undefined, + includeAllTools: undefined, + filters: [], + capabilities: { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + }; + + it('should parse basic filter options from query string', () => { + const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + ]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }); + }); + + it('should parse exclusion filters from query string', () => { + const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'operation', op: 'exclude', value: 'write' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); + + it('should parse client option from query string', () => { + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should parse client capabilities from query string', () => { + const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 40, + }); + }); + + it('should parse no-capability options from query string', () => { + const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: false, + validJson: true, + refs: false, + unions: true, + formats: false, + toolNameLength: undefined, + }); + }); + + it('should parse tools options from query string', () => { + const query = 'tools=dynamic&tools=all'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(true); + expect(result.includeAllTools).toBe(true); + }); + + it('should parse no-tools options from query string', () => { + const query = 'tools=dynamic&tools=all&no_tools=dynamic'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(false); + expect(result.includeAllTools).toBe(true); + }); + + it('should handle array values in query string', () => { + const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ]); + }); + + it('should merge with default options', () => { + const defaultWithFilters = { + ...defaultOptions, + filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], + client: 'cursor' as const, + includeDynamicTools: true, + }; + + const query = 'tool=new-tool&resource=new-resource'; + const result = parseQueryOptions(defaultWithFilters, query); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'existing-tag' }, + { type: 'resource', op: 'include', value: 'new-resource' }, + { type: 'tool', op: 'include', value: 'new-tool' }, + ]); + + expect(result.client).toBe('cursor'); + expect(result.includeDynamicTools).toBe(true); + }); + + it('should override client from default options', () => { + const defaultWithClient = { + ...defaultOptions, + client: 'cursor' as const, + }; + + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultWithClient, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should merge capabilities with default options', () => { + const defaultWithCapabilities = { + ...defaultOptions, + capabilities: { + topLevelUnions: false, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: 30, + }, + }; + + const query = 'capability=top-level-unions&no_capability=refs'; + const result = parseQueryOptions(defaultWithCapabilities, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: false, + refs: false, + unions: true, + formats: true, + toolNameLength: 30, + }); + }); + + it('should handle empty query string', () => { + const query = ''; + const result = parseQueryOptions(defaultOptions, query); + + expect(result).toEqual(defaultOptions); + }); + + it('should handle invalid query string gracefully', () => { + const query = 'invalid=value&operation=invalid-operation'; + + // Should throw due to Zod validation for invalid operation + expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); + }); + + it('should preserve default undefined values when not specified', () => { + const defaultWithUndefined = { + ...defaultOptions, + client: undefined, + includeDynamicTools: undefined, + includeAllTools: undefined, + }; + + const query = 'tool=test-tool'; + const result = parseQueryOptions(defaultWithUndefined, query); + + expect(result.client).toBeUndefined(); + expect(result.includeDynamicTools).toBeFalsy(); + expect(result.includeAllTools).toBeFalsy(); + }); + + it('should handle complex query with mixed include and exclude filters', () => { + const query = + 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'include-res' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'include-tag' }, + { type: 'tool', op: 'include', value: 'include-tool' }, + { type: 'resource', op: 'exclude', value: 'exclude-res' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); +}); + +describe('parseEmbeddedJSON', () => { + it('should not change non-string values', () => { + const args = { + numberProp: 42, + booleanProp: true, + objectProp: { nested: 'value' }, + arrayProp: [1, 2, 3], + nullProp: null, + undefinedProp: undefined, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberProp']).toBe(42); + expect(result['booleanProp']).toBe(true); + expect(result['objectProp']).toEqual({ nested: 'value' }); + expect(result['arrayProp']).toEqual([1, 2, 3]); + expect(result['nullProp']).toBe(null); + expect(result['undefinedProp']).toBe(undefined); + }); + + it('should parse valid JSON objects in string properties', () => { + const args = { + jsonObjectString: '{"key": "value", "number": 123}', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since changes were made + expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); + expect(result['regularString']).toBe('not json'); + }); + + it('should leave invalid JSON in string properties unchanged', () => { + const args = { + invalidJson1: '{"key": value}', // Missing quotes around value + invalidJson2: '{key: "value"}', // Missing quotes around key + invalidJson3: '{"key": "value",}', // Trailing comma + invalidJson4: 'just a regular string', + emptyString: '', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['invalidJson1']).toBe('{"key": value}'); + expect(result['invalidJson2']).toBe('{key: "value"}'); + expect(result['invalidJson3']).toBe('{"key": "value",}'); + expect(result['invalidJson4']).toBe('just a regular string'); + expect(result['emptyString']).toBe(''); + }); + + it('should not parse JSON primitives in string properties', () => { + const args = { + numberString: '123', + floatString: '45.67', + negativeNumberString: '-89', + booleanTrueString: 'true', + booleanFalseString: 'false', + nullString: 'null', + jsonArrayString: '[1, 2, 3, "test"]', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberString']).toBe('123'); + expect(result['floatString']).toBe('45.67'); + expect(result['negativeNumberString']).toBe('-89'); + expect(result['booleanTrueString']).toBe('true'); + expect(result['booleanFalseString']).toBe('false'); + expect(result['nullString']).toBe('null'); + expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); + expect(result['regularString']).toBe('not json'); + }); + + it('should handle mixed valid objects and other JSON types', () => { + const args = { + validObject: '{"success": true}', + invalidObject: '{"missing": quote}', + validNumber: '42', + validArray: '[1, 2, 3]', + keepAsString: 'hello world', + nonString: 123, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since some changes were made + expect(result['validObject']).toEqual({ success: true }); + expect(result['invalidObject']).toBe('{"missing": quote}'); + expect(result['validNumber']).toBe('42'); // Not parsed, remains string + expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string + expect(result['keepAsString']).toBe('hello world'); + expect(result['nonString']).toBe(123); + }); + + it('should return original object when no strings are present', () => { + const args = { + number: 42, + boolean: true, + object: { key: 'value' }, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); + + it('should return original object when all strings are invalid JSON', () => { + const args = { + string1: 'hello', + string2: 'world', + string3: 'not json at all', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); +}); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts new file mode 100644 index 00000000..cfff24a2 --- /dev/null +++ b/packages/mcp-server/tests/tools.test.ts @@ -0,0 +1,225 @@ +import { Endpoint, Filter, Metadata, query } from '../src/tools'; + +describe('Endpoint filtering', () => { + const endpoints: Endpoint[] = [ + endpoint({ + resource: 'user', + operation: 'read', + tags: ['admin'], + toolName: 'retrieve_user', + }), + endpoint({ + resource: 'user.profile', + operation: 'write', + tags: [], + toolName: 'create_user_profile', + }), + endpoint({ + resource: 'user.profile', + operation: 'read', + tags: [], + toolName: 'get_user_profile', + }), + endpoint({ + resource: 'user.roles.permissions', + operation: 'write', + tags: ['admin', 'security'], + toolName: 'update_user_role_permissions', + }), + endpoint({ + resource: 'documents.metadata.tags', + operation: 'write', + tags: ['taxonomy', 'metadata'], + toolName: 'create_document_metadata_tags', + }), + endpoint({ + resource: 'organization.settings', + operation: 'read', + tags: ['admin', 'configuration'], + toolName: 'get_organization_settings', + }), + ]; + + const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ + { + name: 'match none', + filters: [], + expected: [], + }, + + // Resource tests + { + name: 'simple resource', + filters: [{ type: 'resource', op: 'include', value: 'user' }], + expected: ['retrieve_user'], + }, + { + name: 'exclude resource', + filters: [{ type: 'resource', op: 'exclude', value: 'user' }], + expected: [ + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'create_document_metadata_tags', + 'get_organization_settings', + ], + }, + { + name: 'resource and subresources', + filters: [{ type: 'resource', op: 'include', value: 'user*' }], + expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'just subresources', + filters: [{ type: 'resource', op: 'include', value: 'user.*' }], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'specific subresource', + filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], + expected: ['update_user_role_permissions'], + }, + { + name: 'deep wildcard match', + filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], + expected: ['create_document_metadata_tags'], + }, + + // Operation tests + { + name: 'read operation', + filters: [{ type: 'operation', op: 'include', value: 'read' }], + expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], + }, + { + name: 'write operation', + filters: [{ type: 'operation', op: 'include', value: 'write' }], + expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], + }, + { + name: 'resource and operation combined', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ], + expected: ['get_user_profile'], + }, + + // Tag tests + { + name: 'admin tag', + filters: [{ type: 'tag', op: 'include', value: 'admin' }], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'taxonomy tag', + filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], + expected: ['create_document_metadata_tags'], + }, + { + name: 'multiple tags (OR logic)', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'include', value: 'security' }, + ], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'excluding a tag', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'exclude', value: 'security' }, + ], + expected: ['retrieve_user', 'get_organization_settings'], + }, + + // Tool name tests + { + name: 'tool name match', + filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], + expected: ['get_organization_settings'], + }, + { + name: 'two tools match', + filters: [ + { type: 'tool', op: 'include', value: 'get_organization_settings' }, + { type: 'tool', op: 'include', value: 'create_user_profile' }, + ], + expected: ['create_user_profile', 'get_organization_settings'], + }, + { + name: 'excluding tool by name', + filters: [ + { type: 'resource', op: 'include', value: 'user*' }, + { type: 'tool', op: 'exclude', value: 'retrieve_user' }, + ], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + + // Complex combinations + { + name: 'complex filter: read operations with admin tag', + filters: [ + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + { + name: 'complex filter: user resources with no tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'exclude', value: 'admin' }, + ], + expected: ['create_user_profile', 'get_user_profile'], + }, + { + name: 'complex filter: user resources and tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + ]; + + tests.forEach((test) => { + it(`filters by ${test.name}`, () => { + const filtered = query(test.filters, endpoints); + expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); + }); + }); +}); + +function endpoint({ + resource, + operation, + tags, + toolName, +}: { + resource: string; + operation: Metadata['operation']; + tags: string[]; + toolName: string; +}): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, + handler: jest.fn(), + }; +} diff --git a/packages/mcp-server/tsc-multi.json b/packages/mcp-server/tsc-multi.json new file mode 100644 index 00000000..4facad5a --- /dev/null +++ b/packages/mcp-server/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + { "extname": ".js", "module": "commonjs" }, + { "extname": ".mjs", "module": "esnext" } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json new file mode 100644 index 00000000..047e086f --- /dev/null +++ b/packages/mcp-server/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "@imagekit/nodejs-mcp/*": ["dist/src/*"], + "@imagekit/nodejs-mcp": ["dist/src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/packages/mcp-server/tsconfig.dist-src.json b/packages/mcp-server/tsconfig.dist-src.json new file mode 100644 index 00000000..e9f2d70b --- /dev/null +++ b/packages/mcp-server/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "es2015", + "lib": ["DOM"], + "moduleResolution": "node" + } +} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json new file mode 100644 index 00000000..ddbe0074 --- /dev/null +++ b/packages/mcp-server/tsconfig.json @@ -0,0 +1,37 @@ +{ + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "@imagekit/nodejs-mcp/*": ["src/*"], + "@imagekit/nodejs-mcp": ["src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + + "skipLibCheck": true + } +} diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock new file mode 100644 index 00000000..707a2de8 --- /dev/null +++ b/packages/mcp-server/yarn.lock @@ -0,0 +1,3606 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" + integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" + integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helpers" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" + integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== + dependencies: + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.1": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" + integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" + integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" + integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.1", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" + integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" + integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cloudflare/cabidela@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@cloudflare/cabidela/-/cabidela-0.2.4.tgz#9a3e9212e636a24d796a8f16741c24885b326a1a" + integrity sha512-u/1OwwqfcMvjmUFOcb6QtFzVVGpncHJxwl254wjzp0JC5CUlBkV6r5BbRrHI5ZYJEAgu8NeeorirxngmMFPZjQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" + integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@modelcontextprotocol/sdk@^1.11.5": + version "1.17.3" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" + integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== + dependencies: + ajv "^6.12.6" + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.5" + eventsource "^3.0.2" + eventsource-parser "^3.0.0" + express "^5.0.1" + express-rate-limit "^7.5.0" + pkce-challenge "^5.0.0" + raw-body "^3.0.0" + zod "^3.23.8" + zod-to-json-schema "^3.24.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@ts-morph/common@~0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" + integrity sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q== + dependencies: + fast-glob "^3.2.12" + minimatch "^7.4.3" + mkdirp "^2.1.6" + path-browserify "^1.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" + integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^5.0.0": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" + integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" + integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.4.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*": + version "22.15.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" + integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== + dependencies: + undici-types "~6.21.0" + +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" + integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== + dependencies: + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== + dependencies: + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== + +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== + dependencies: + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.12.4, ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +body-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" + integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.6.3" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" + integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== + dependencies: + caniuse-lite "^1.0.30001716" + electron-to-chromium "^1.5.149" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.1.2, bytes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001716: + version "1.0.30001717" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" + integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +code-block-writer@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" + integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +depd@2.0.0, depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.149: + version "1.5.151" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz#5edd6c17e1b2f14b4662c41b9379f96cc8c2bb7c" + integrity sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-prettier@^5.0.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b" + integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.0" + +eslint-plugin-unused-imports@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d" + integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^8.49.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventsource-parser@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8" + integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA== + +eventsource-parser@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" + integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== + +eventsource@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" + integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + dependencies: + eventsource-parser "^3.0.1" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express-rate-limit@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" + integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== + +express@^5.0.1, express@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" + integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.0" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.12, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@2.0.0, http-errors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.4.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": + version "0.8.6" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^7.4.3: + version "7.4.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" + integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-all@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" + integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== + dependencies: + p-map "^4.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkce-challenge@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" + integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + dependencies: + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1, statuses@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" + integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== + dependencies: + "@pkgr/core" "^0.2.3" + tslib "^2.8.1" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-api-utils@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +ts-jest@^29.1.0: + version "29.3.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.2.tgz#0576cdf0a507f811fe73dcd16d135ce89f8156cb" + integrity sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.1" + type-fest "^4.39.1" + yargs-parser "^21.1.1" + +ts-morph@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-19.0.0.tgz#43e95fb0156c3fe3c77c814ac26b7d0be2f93169" + integrity sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ== + dependencies: + "@ts-morph/common" "~0.20.0" + code-block-writer "^12.0.0" + +ts-node@^10.5.0: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz": + version "1.1.8" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04" + dependencies: + debug "^4.3.7" + fast-glob "^3.3.2" + get-stdin "^8.0.0" + p-all "^3.0.0" + picocolors "^1.1.1" + signal-exit "^3.0.7" + string-to-stream "^3.0.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" + +tsconfig-paths@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.39.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.0, type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1, vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + 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" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: + version "3.24.5" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" + integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== + +zod-validation-error@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" + integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== + +zod@^3.23.8: + version "3.24.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" + integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== + +zod@^3.25.20: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/sample/README.md b/sample/README.md deleted file mode 100644 index bb0fdc28..00000000 --- a/sample/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Running the sample application - -### Step 1: Install dependencies -```bash -npm install -``` - -### Step 2: Enter account details -Open `index.js` and fill the account details: -```js -const CONFIG_OPTIONS = { - publicKey: "your_public_api_key", - privateKey: "your_private_api_key", - urlEndpoint: "https://ik.imagekit.io/your_imagekit_id/", -}; -``` - -### Step 3: -Run the `index.js` \ No newline at end of file diff --git a/sample/index.js b/sample/index.js deleted file mode 100644 index 032f2d96..00000000 --- a/sample/index.js +++ /dev/null @@ -1,292 +0,0 @@ -const ImageKit = require("imagekit"); -const fs = require("fs"); -const path = require("path"); - -const CONFIG_OPTIONS = { - publicKey: "your_public_api_key", - privateKey: "your_private_api_key", - urlEndpoint: "https://ik.imagekit.io/your_imagekit_id/", -}; - -const FILE_PATH = path.resolve(__dirname, "./test_image.jpg"), - FILE_NAME = "test_image", - IMG_URL = - "https://images.pexels.com/photos/247676/pexels-photo-247676.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260"; - -const sampleApp = async () => { - try { - const imagekit = new ImageKit(CONFIG_OPTIONS); - - // Uploading images through binary - let i = 0; - while (i < 8) { - const response = await uploadLocalFile(imagekit, FILE_PATH, `${FILE_NAME}_bin_${i + 1}`); - console.log(`Binary upload response # ${i + 1}:`, JSON.stringify(response, undefined, 2), "\n"); - i++; - } - - // Uploading images with base64 - const uploadResponse_base64 = await uploadFileBase64(imagekit, FILE_PATH, `${FILE_NAME}_base64`); - console.log(`Base64 upload response:`, JSON.stringify(uploadResponse_base64, undefined, 2), "\n"); - - // Uploading images with buffer - const uploadResponse_buffer = await uploadFileBuffer(imagekit, FILE_PATH, `${FILE_NAME}_buffer`); - console.log(`Buffer upload response:`, JSON.stringify(uploadResponse_buffer, undefined, 2), "\n"); - - // Uploading images with URL - const uploadResponse_url = await uploadFileURL(imagekit, IMG_URL, `${FILE_NAME}_url`); - console.log(`URL upload response:`, JSON.stringify(uploadResponse_url, undefined, 2), "\n"); - - // Listing Files - const filesList = await listFiles(imagekit, 12, 0); - console.log("List of first 10 files: ", JSON.stringify(filesList, undefined, 2), "\n"); - - // Generating URLs - const imageURL = imagekit.url({ - path: filesList[0].filePath, - transformation: [ - { - height: 300, - width: 400, - }, - ], - }); - console.log("Url for first image transformed with height: 300, width: 400: ", imageURL, "\n"); - - var signedUrl = imagekit.url({ - path: filesList[0].filePath, - signed: true, - transformation: [ - { - height: 300, - width: 400, - }, - ], - }); - console.log("Signed Url for first image transformed with height: 300, width: 400: ", signedUrl, "\n"); - - // Get File Details - const fileDetails_1 = await getFileDetails(imagekit, filesList[0].fileId); - console.log("File Details fetched: ", JSON.stringify(fileDetails_1, undefined, 2), "\n"); - - // Get File Metadata - const fileMetadata_1 = await getFileMetadata(imagekit, filesList[0].fileId); - const fileMetadata_2 = await getFileMetadata(imagekit, filesList[1].fileId); - console.log("File metadata fetched: ", JSON.stringify(fileMetadata_1, undefined, 2), "\n"); - - // Update File Details - const fileUpdateResponse = await updateFileDetails( - imagekit, - filesList[0].fileId, - ["buildings", "day"], - "10,10,100,100", - //Uncomment to send extensions parameter - // [ - // { - // name: "google-auto-tagging", - // maxTags: 5, - // minConfidence: 95 - // } - // ] - ); - console.log("File Update Response: ", JSON.stringify(fileUpdateResponse, undefined, 2), "\n"); - - // pHash Distance - console.log(fileMetadata_1.pHash, fileMetadata_2.pHash); - const pHashDistance = imagekit.pHashDistance(fileMetadata_1.pHash, fileMetadata_2.pHash); - console.log(`pHash distance: ${pHashDistance}`, "\n"); - - // purge Cache and purgeCache status - const purgeCacheResponse = await purgeCache(imagekit, filesList[0].url); - console.log("Purge Cache Response: ", JSON.stringify(purgeCacheResponse, undefined, 2), "\n"); - - const purgeStatus = await getPurgeCacheStatus(imagekit, purgeCacheResponse.requestId); - console.log("Purge Response: ", JSON.stringify(purgeStatus, undefined, 2), "\n"); - - // Bulk add tags - let fileIds = filesList.map((file) => file.fileId); - fileIds.shift(); - var tags = ["red", "blue"]; - const bulkAddTagsResponse = await bulkAddTags(imagekit, fileIds, tags); - console.log("Bulk add tags response: ", bulkAddTagsResponse, "\n"); - - // Bulk remove tags - const bulkRemoveTagsResponse = await bulkRemoveTags(imagekit, fileIds, tags); - console.log("Bulk remove tags response: ", bulkRemoveTagsResponse, "\n"); - - // Create folder - const createFolderResponse_1 = await createFolder(imagekit, "folder1", "/"); - const createFolderResponse_2 = await createFolder(imagekit, "folder2", "/"); - console.log("Folder creation response: ", createFolderResponse_2, "\n"); - - // Copy file - const copyFileResponse = await copyFile(imagekit, fileDetails_1.filePath, "/folder1/"); - console.log("File copy response: ", copyFileResponse, "\n"); - - // Move file - const moveFileResponse = await moveFile(imagekit, `/folder1/${fileDetails_1.name}`, "/folder2/"); - console.log("File move response: ", moveFileResponse, "\n"); - - // Copy folder - const copyFolderResponse = await copyFolder(imagekit, "/folder2", "/folder1/"); - console.log("Copy folder response: ", JSON.stringify(copyFolderResponse, undefined, 2), "\n"); - - // Move folder - const moveFolderResponse = await moveFolder(imagekit, "/folder1", "/folder2/"); - console.log("Move folder response: ", JSON.stringify(moveFolderResponse, undefined, 2), "\n"); - - // Get bulk job status - const getBulkJobStatusResponse = await getBulkJobStatus(imagekit, moveFolderResponse.jobId); - console.log("Bulk job status response: ", JSON.stringify(getBulkJobStatusResponse), "\n"); - - // Delete folder - const deleteFolderResponse = await deleteFolder(imagekit, "/folder2/"); - console.log("Delete folder response: ", deleteFolderResponse, "\n"); - - // Deleting Files - const deleteResponse = await deleteFile(imagekit, fileDetails_1.fileId); - console.log("Deletion response: ", deleteResponse, "\n"); - - // Bulk Delete Files - const bulkDeleteResponse = await bulkDeleteFiles(imagekit, fileIds); - console.log("Bulk deletion response: ", bulkDeleteResponse, "\n"); - - //Authentication token - const authenticationParameters = imagekit.getAuthenticationParameters("your_token"); - console.log("Authentication Parameters: ", JSON.stringify(authenticationParameters, undefined, 2), "\n"); - - process.exit(0); - } catch (err) { - console.log("Encounterted Error: ", JSON.stringify(err, undefined, 2)); - process.exit(1); - } -}; - -const uploadLocalFile = async (imagekitInstance, filePath, fileName) => { - const file = fs.createReadStream(filePath); - const response = await imagekitInstance.upload({ file, fileName }); - return response; -}; - -const uploadFileBuffer = async (imagekitInstance, filePath, fileName) => { - const buffer = fs.readFileSync(filePath); - const response = await imagekitInstance.upload({ file: buffer, fileName }); - return response; -}; - -const uploadFileBase64 = async (imagekitInstance, filePath, fileName) => { - const file_base64 = fs.readFileSync(filePath, "base64"); - //Uncomment to send extensions parameter - // var extensions = [ - // { - // name: "google-auto-tagging", - // maxTags: 5, - // minConfidence: 95 - // } - // ]; - const response = await imagekitInstance.upload({ file: file_base64, fileName/*, extensions*/}); - return response; -}; - -const uploadFileURL = async (imagekitInstance, url, fileName) => { - const response = await imagekitInstance.upload({ file: url, fileName }); - return response; -}; - -const listFiles = async (imagekitInstance, limit = 10, skip = 0) => { - const response = await imagekitInstance.listFiles({ - limit, - skip, - }); - return response; -}; - -const getFileDetails = async (imagekitInstance, fileId) => { - const response = await imagekitInstance.getFileDetails(fileId); - return response; -}; - -const getFileMetadata = async (imagekitInstance, fileId) => { - const response = await imagekitInstance.getFileMetadata(fileId); - return response; -}; - -const updateFileDetails = async (imagekitInstance, fileId, tags = [], customCoordinates = "", extensions = [], webhookUrl = "") => { - let options = {}; - if (Array.isArray(tags) && tags.length > 0) Object.assign(options, { tags }); - if (typeof customCoordinates === "string" && customCoordinates.length > 0) - Object.assign(options, { customCoordinates }); - if (Array.isArray(extensions) && extensions.length > 0) - Object.assign(options,{ extensions }); - if (typeof webhookUrl === "string" && webhookUrl.length > 0) - Object.assign(options,{ webhookUrl }) - const response = await imagekitInstance.updateFileDetails(fileId, options); - return response; -}; - -const purgeCache = async (imagekitInstance, url) => { - const response = await imagekitInstance.purgeCache(url); - return response; -}; - -const getPurgeCacheStatus = async (imagekitInstance, requestId) => { - const response = await imagekitInstance.getPurgeCacheStatus(requestId); - return response; -}; - -const deleteFile = async (imagekitInstance, fileId) => { - const response = await imagekitInstance.deleteFile(fileId); - return "success"; -}; - -const bulkDeleteFiles = async (imagekitInstance, fileIds) => { - const response = await imagekitInstance.bulkDeleteFiles(fileIds); - return "success"; -}; - -const bulkAddTags = async (imagekitInstance, fileIds, tags) => { - const response = await imagekitInstance.bulkAddTags(fileIds, tags); - return "success"; -}; - -const bulkRemoveTags = async (imagekitInstance, fileIds, tags) => { - const response = await imagekitInstance.bulkRemoveTags(fileIds, tags); - return "success"; -}; - -const copyFile = async (imagekitInstance, sourceFilePath, destinationPath) => { - const response = await imagekitInstance.copyFile(sourceFilePath, destinationPath); - return "success"; -}; - -const moveFile = async (imagekitInstance, sourceFilePath, destinationPath) => { - const response = await imagekitInstance.moveFile(sourceFilePath, destinationPath); - return "success"; -}; - -const copyFolder = async (imagekitInstance, sourceFolderPath, destinationPath) => { - const response = await imagekitInstance.copyFolder(sourceFolderPath, destinationPath); - return response; -}; - -const moveFolder = async (imagekitInstance, sourceFolderPath, destinationPath) => { - const response = await imagekitInstance.moveFolder(sourceFolderPath, destinationPath); - return response; -}; - -const createFolder = async (imagekitInstance, folderName, parentFolderPath) => { - const response = await imagekitInstance.createFolder(folderName, parentFolderPath); - return "success"; -}; - -const deleteFolder = async (imagekitInstance, folderPath) => { - const response = await imagekitInstance.deleteFolder(folderPath); - return "success"; -}; - -const getBulkJobStatus = async (imagekitInstance, jobId) => { - const response = await imagekitInstance.getBulkJobStatus(jobId); - return response; -}; - -sampleApp(); diff --git a/sample/package.json b/sample/package.json deleted file mode 100644 index fb22c514..00000000 --- a/sample/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "dependencies": { - "imagekit": "*" - }, - "author": "", - "license": "ISC" -} \ No newline at end of file diff --git a/sample/test_image.jpg b/sample/test_image.jpg deleted file mode 100644 index 8102e278be0fe0aabae92a9ea9f3a3a62c43568d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199185 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 diff --git a/scripts/bootstrap b/scripts/bootstrap new file mode 100755 index 00000000..062a0349 --- /dev/null +++ b/scripts/bootstrap @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then + brew bundle check >/dev/null 2>&1 || { + echo "==> Installing Homebrew dependencies…" + brew bundle + } +fi + +echo "==> Installing Node dependencies…" + +PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") + +$PACKAGE_MANAGER install "$@" diff --git a/scripts/build b/scripts/build new file mode 100755 index 00000000..fe159668 --- /dev/null +++ b/scripts/build @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +cd "$(dirname "$0")/.." + +node scripts/utils/check-version.cjs + +# Build into dist and will publish the package from there, +# so that src/resources/foo.ts becomes /resources/foo.js +# This way importing from `"@imagekit/nodejs/resources/foo"` works +# even with `"moduleResolution": "node"` + +rm -rf dist; mkdir dist +# Copy src to dist/src and build from dist/src into dist, so that +# the source map for index.js.map will refer to ./src/index.ts etc +cp -rp src README.md dist +for file in LICENSE CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done +if [ -e "bin/cli" ]; then + mkdir -p dist/bin + cp -p "bin/cli" dist/bin/; +fi +if [ -e "bin/migration-config.json" ]; then + mkdir -p dist/bin + cp -p "bin/migration-config.json" dist/bin/; +fi +# this converts the export map paths for the dist directory +# and does a few other minor things +node scripts/utils/make-dist-package-json.cjs > dist/package.json + +# build to .js/.mjs/.d.ts files +./node_modules/.bin/tsc-multi +# we need to patch index.js so that `new module.exports()` works for cjs backwards +# compat. No way to get that from index.ts because it would cause compile errors +# when building .mjs +node scripts/utils/fix-index-exports.cjs +cp tsconfig.dist-src.json dist/src/tsconfig.json + +node scripts/utils/postprocess-files.cjs + +# make sure that nothing crashes when we require the output CJS or +# import the output ESM +(cd dist && node -e 'require("@imagekit/nodejs")') +(cd dist && node -e 'import("@imagekit/nodejs")' --input-type=module) + +if [ -e ./scripts/build-deno ] +then + ./scripts/build-deno +fi +# build all sub-packages +for dir in packages/*; do + if [ -d "$dir" ]; then + (cd "$dir" && yarn install && yarn build) + fi +done diff --git a/scripts/build-all b/scripts/build-all new file mode 100755 index 00000000..4e5ac01f --- /dev/null +++ b/scripts/build-all @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# build-all is deprecated, use build instead + +bash ./scripts/build diff --git a/scripts/format b/scripts/format new file mode 100755 index 00000000..7a756401 --- /dev/null +++ b/scripts/format @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running eslint --fix" +./node_modules/.bin/eslint --fix . + +echo "==> Running prettier --write" +# format things eslint didn't +./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 00000000..3ffb78a6 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running eslint" +./node_modules/.bin/eslint . + +echo "==> Building" +./scripts/build + +echo "==> Checking types" +./node_modules/typescript/bin/tsc + +echo "==> Running Are The Types Wrong?" +./node_modules/.bin/attw --pack dist -f json >.attw.json || true +node scripts/utils/attw-report.cjs + +echo "==> Running publint" +./node_modules/.bin/publint dist diff --git a/scripts/mock b/scripts/mock new file mode 100755 index 00000000..0b28f6ea --- /dev/null +++ b/scripts/mock @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [[ -n "$1" && "$1" != '--'* ]]; then + URL="$1" + shift +else + URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" +fi + +# Check if the URL is empty +if [ -z "$URL" ]; then + echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" + exit 1 +fi + +echo "==> Starting mock server with URL ${URL}" + +# Run prism mock on the given spec +if [ "$1" == "--daemon" ]; then + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + + # Wait for server to come online + echo -n "Waiting for server" + while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + echo -n "." + sleep 0.1 + done + + if grep -q "✖ fatal" ".prism.log"; then + cat .prism.log + exit 1 + fi + + echo +else + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" +fi diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 00000000..50e93fef --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,102 @@ +/** + * Called from the `create-releases.yml` workflow with the output + * of the release please action as the first argument. + * + * Example JSON input: + * + * ```json + { + "releases_created": "true", + "release_created": "true", + "id": "137967744", + "name": "sdk: v0.14.5", + "tag_name": "sdk-v0.14.5", + "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "body": "## 0.14.5 (2024-01-22)\n\n...", + "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5", + "draft": "false", + "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}", + "path": ".", + "version": "0.14.5", + "major": "0", + "minor": "14", + "patch": "5", + "packages/additional-sdk--release_created": "true", + "packages/additional-sdk--id": "137967756", + "packages/additional-sdk--name": "additional-sdk: v0.5.2", + "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2", + "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...", + "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2", + "packages/additional-sdk--draft": "false", + "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}", + "packages/additional-sdk--path": "packages/additional-sdk", + "packages/additional-sdk--version": "0.5.2", + "packages/additional-sdk--major": "0", + "packages/additional-sdk--minor": "5", + "packages/additional-sdk--patch": "2", + "paths_released": "[\".\",\"packages/additional-sdk\"]" + } + ``` + */ + +import { execSync } from 'child_process'; +import path from 'path'; + +function main() { + const data = process.argv[2] ?? process.env['DATA']; + if (!data) { + throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`); + } + + const rootDir = path.join(__dirname, '..'); + console.log('root dir', rootDir); + console.log(`publish-packages called with ${data}`); + + const outputs = JSON.parse(data); + + const rawPaths = outputs.paths_released; + + if (!rawPaths) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs to contain a truthy `paths_released` property'); + } + if (typeof rawPaths !== 'string') { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be a JSON string'); + } + + const paths = JSON.parse(rawPaths); + if (!Array.isArray(paths)) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be an array'); + } + if (!paths.length) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to contain at least one entry'); + } + + const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm'); + console.log('Using publish script at', publishScriptPath); + + console.log('Ensuring root package is built'); + console.log(`$ yarn build`); + execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' }); + + for (const relPackagePath of paths) { + console.log('\n'); + + const packagePath = path.join(rootDir, relPackagePath); + console.log(`Publishing in directory: ${packagePath}`); + + console.log(`$ yarn install`); + execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + + console.log(`$ bash ${publishScriptPath}`); + execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + } + + console.log('Finished publishing packages'); +} + +main(); diff --git a/scripts/test b/scripts/test new file mode 100755 index 00000000..7bce0516 --- /dev/null +++ b/scripts/test @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +function prism_is_running() { + curl --silent "http://localhost:4010" >/dev/null 2>&1 +} + +kill_server_on_port() { + pids=$(lsof -t -i tcp:"$1" || echo "") + if [ "$pids" != "" ]; then + kill "$pids" + echo "Stopped $pids." + fi +} + +function is_overriding_api_base_url() { + [ -n "$TEST_API_BASE_URL" ] +} + +if ! is_overriding_api_base_url && ! prism_is_running ; then + # When we exit this script, make sure to kill the background mock server process + trap 'kill_server_on_port 4010' EXIT + + # Start the dev server + ./scripts/mock --daemon +fi + +if is_overriding_api_base_url ; then + echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" + echo +elif ! prism_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" + echo -e "running against your OpenAPI spec." + echo + echo -e "To run the server, pass in the path or url of your OpenAPI" + echo -e "spec to the prism command:" + echo + echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo + + exit 1 +else + echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo +fi + +echo "==> Running tests" +./node_modules/.bin/jest "$@" diff --git a/scripts/utils/attw-report.cjs b/scripts/utils/attw-report.cjs new file mode 100644 index 00000000..b3477c0e --- /dev/null +++ b/scripts/utils/attw-report.cjs @@ -0,0 +1,24 @@ +const fs = require('fs'); +const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) + .flat() + .filter( + (problem) => + !( + // This is intentional, if the user specifies .mjs they get ESM. + ( + (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || + // This is intentional for backwards compat reasons. + (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) || + // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules + // folders to better support various runtimes without triggering automatic type acquisition. + (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules')) + ) + ), + ); +fs.unlinkSync('.attw.json'); +if (problems.length) { + process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); + process.exitCode = 1; +} else { + process.stdout.write('Types ok!\n'); +} diff --git a/scripts/utils/check-is-in-git-install.sh b/scripts/utils/check-is-in-git-install.sh new file mode 100755 index 00000000..1354eb43 --- /dev/null +++ b/scripts/utils/check-is-in-git-install.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Check if you happen to call prepare for a repository that's already in node_modules. +[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || +# The name of the containing directory that 'npm` uses, which looks like +# $HOME/.npm/_cacache/git-cloneXXXXXX +[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || +# The name of the containing directory that 'yarn` uses, which looks like +# $(yarn cache dir)/.tmp/XXXXX +[ "$(basename "$(dirname "$PWD")")" = '.tmp' ] diff --git a/scripts/utils/check-version.cjs b/scripts/utils/check-version.cjs new file mode 100644 index 00000000..86c56dfd --- /dev/null +++ b/scripts/utils/check-version.cjs @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +const main = () => { + const pkg = require('../../package.json'); + const version = pkg['version']; + if (!version) throw 'The version property is not set in the package.json file'; + if (typeof version !== 'string') { + throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; + } + + const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); + const contents = fs.readFileSync(versionFile, 'utf8'); + const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); + fs.writeFileSync(versionFile, output); +}; + +if (require.main === module) { + main(); +} diff --git a/scripts/utils/fix-index-exports.cjs b/scripts/utils/fix-index-exports.cjs new file mode 100644 index 00000000..e5e10b3e --- /dev/null +++ b/scripts/utils/fix-index-exports.cjs @@ -0,0 +1,17 @@ +const fs = require('fs'); +const path = require('path'); + +const indexJs = + process.env['DIST_PATH'] ? + path.resolve(process.env['DIST_PATH'], 'index.js') + : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); + +let before = fs.readFileSync(indexJs, 'utf8'); +let after = before.replace( + /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, + `exports = module.exports = function (...args) { + return new exports.default(...args) + } + $1`.replace(/^ /gm, ''), +); +fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/scripts/utils/git-swap.sh b/scripts/utils/git-swap.sh new file mode 100755 index 00000000..79d1888e --- /dev/null +++ b/scripts/utils/git-swap.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -exuo pipefail +# the package is published to NPM from ./dist +# we want the final file structure for git installs to match the npm installs, so we + +# delete everything except ./dist and ./node_modules +find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + + +# move everything from ./dist to . +mv dist/* . + +# delete the now-empty ./dist +rmdir dist diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs new file mode 100644 index 00000000..4d6634ea --- /dev/null +++ b/scripts/utils/make-dist-package-json.cjs @@ -0,0 +1,29 @@ +const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); + +function processExportMap(m) { + for (const key in m) { + const value = m[key]; + if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); + else processExportMap(value); + } +} +processExportMap(pkgJson.exports); + +for (const key of ['types', 'main', 'module']) { + if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); +} +// Fix bin paths if present +if (pkgJson.bin) { + for (const key in pkgJson.bin) { + if (typeof pkgJson.bin[key] === 'string') { + pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './'); + } + } +} + +delete pkgJson.devDependencies; +delete pkgJson.scripts.prepack; +delete pkgJson.scripts.prepublishOnly; +delete pkgJson.scripts.prepare; + +console.log(JSON.stringify(pkgJson, null, 2)); diff --git a/scripts/utils/postprocess-files.cjs b/scripts/utils/postprocess-files.cjs new file mode 100644 index 00000000..deae575e --- /dev/null +++ b/scripts/utils/postprocess-files.cjs @@ -0,0 +1,94 @@ +// @ts-check +const fs = require('fs'); +const path = require('path'); + +const distDir = + process.env['DIST_PATH'] ? + path.resolve(process.env['DIST_PATH']) + : path.resolve(__dirname, '..', '..', 'dist'); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +async function postprocess() { + for await (const file of walk(distDir)) { + if (!/(\.d)?[cm]?ts$/.test(file)) continue; + + const code = await fs.promises.readFile(file, 'utf8'); + + // strip out lib="dom", types="node", and types="react" references; these + // are needed at build time, but would pollute the user's TS environment + const transformed = code.replace( + /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', + ); + + if (transformed !== code) { + console.error(`wrote ${path.relative(process.cwd(), file)}`); + await fs.promises.writeFile(file, transformed, 'utf8'); + } + } + + const newExports = { + '.': { + require: { + types: './index.d.ts', + default: './index.js', + }, + types: './index.d.mts', + default: './index.mjs', + }, + }; + + for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { + const subpath = './' + entry.name; + newExports[subpath + '/*.mjs'] = { + default: subpath + '/*.mjs', + }; + newExports[subpath + '/*.js'] = { + default: subpath + '/*.js', + }; + newExports[subpath + '/*'] = { + import: subpath + '/*.mjs', + require: subpath + '/*.js', + }; + } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { + const { name, ext } = path.parse(entry.name); + const subpathWithoutExt = './' + name; + const subpath = './' + entry.name; + newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; + const isModule = ext[1] === 'm'; + if (isModule) { + newExports[subpathWithoutExt].import = subpath; + } else { + newExports[subpathWithoutExt].require = subpath; + } + newExports[subpath] = { + default: subpath, + }; + } + } + await fs.promises.writeFile( + 'dist/package.json', + JSON.stringify( + Object.assign( + /** @type {Record} */ ( + JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) + ), + { + exports: newExports, + }, + ), + null, + 2, + ), + ); +} +postprocess(); diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..1d893fde --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(tar -cz "${BUILD_PATH:-dist}" | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/imagekit-typescript/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi diff --git a/src/api-promise.ts b/src/api-promise.ts new file mode 100644 index 00000000..8c775ee6 --- /dev/null +++ b/src/api-promise.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/api-promise instead */ +export * from './core/api-promise'; diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 00000000..9c8c3e2e --- /dev/null +++ b/src/client.ts @@ -0,0 +1,897 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; +import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; +import { uuid4 } from './internal/utils/uuid'; +import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; +import { sleep } from './internal/utils/sleep'; +export type { Logger, LogLevel } from './internal/utils/log'; +import { castToError, isAbortError } from './internal/errors'; +import type { APIResponseProps } from './internal/parse'; +import { getPlatformHeaders } from './internal/detect-platform'; +import * as Shims from './internal/shims'; +import * as Opts from './internal/request-options'; +import { VERSION } from './version'; +import * as Errors from './core/error'; +import * as Uploads from './core/uploads'; +import * as API from './resources/index'; +import { APIPromise } from './core/api-promise'; +import { AssetListParams, AssetListResponse, Assets } from './resources/assets'; +import { + CustomMetadataField, + CustomMetadataFieldCreateParams, + CustomMetadataFieldDeleteResponse, + CustomMetadataFieldListParams, + CustomMetadataFieldListResponse, + CustomMetadataFieldUpdateParams, + CustomMetadataFields, +} from './resources/custom-metadata-fields'; +import { + UnsafeUnwrapWebhookEvent, + UnwrapWebhookEvent, + VideoTransformationAcceptedEvent, + VideoTransformationErrorEvent, + VideoTransformationReadyEvent, + Webhooks, +} from './resources/webhooks'; +import { Accounts } from './resources/accounts/accounts'; +import { Beta } from './resources/beta/beta'; +import { Cache } from './resources/cache/cache'; +import { + File, + FileCopyParams, + FileCopyResponse, + FileMoveParams, + FileMoveResponse, + FileRenameParams, + FileRenameResponse, + FileUpdateParams, + FileUpdateResponse, + FileUploadParams, + FileUploadResponse, + Files, + Folder, + Metadata, +} from './resources/files/files'; +import { + FolderCopyParams, + FolderCopyResponse, + FolderCreateParams, + FolderCreateResponse, + FolderDeleteParams, + FolderDeleteResponse, + FolderMoveParams, + FolderMoveResponse, + FolderRenameParams, + FolderRenameResponse, + Folders, +} from './resources/folders/folders'; +import { type Fetch } from './internal/builtin-types'; +import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; +import { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import { toBase64 } from './internal/utils/base64'; +import { readEnv } from './internal/utils/env'; +import { + type LogLevel, + type Logger, + formatRequestDetails, + loggerFor, + parseLogLevel, +} from './internal/utils/log'; +import { isEmptyObj } from './internal/utils/values'; + +export interface ClientOptions { + /** + * Your ImageKit private key starts with `private_`. + */ + privateAPIKey?: string | undefined; + + /** + * Do not set this, its value is ignored + */ + password?: string | null | undefined; + + /** + * Override the default base URL for the API, e.g., "https://api.example.com/v2/" + * + * Defaults to process.env['IMAGE_KIT_BASE_URL']. + */ + baseURL?: string | null | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * Note that request timeouts are retried by default, so in a worst-case scenario you may wait + * much longer than this timeout before the promise succeeds or fails. + * + * @unit milliseconds + */ + timeout?: number | undefined; + /** + * Additional `RequestInit` options to be passed to `fetch` calls. + * Properties will be overridden by per-request `fetchOptions`. + */ + fetchOptions?: MergedRequestInit | undefined; + + /** + * Specify a custom `fetch` function implementation. + * + * If not provided, we expect that `fetch` is defined globally. + */ + fetch?: Fetch | undefined; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number | undefined; + + /** + * Default headers to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * header to `null` in request options. + */ + defaultHeaders?: HeadersLike | undefined; + + /** + * Default query parameters to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * param to `undefined` in request options. + */ + defaultQuery?: Record | undefined; + + /** + * Set the log level. + * + * Defaults to process.env['IMAGE_KIT_LOG'] or 'warn' if it isn't set. + */ + logLevel?: LogLevel | undefined; + + /** + * Set the logger. + * + * Defaults to globalThis.console. + */ + logger?: Logger | undefined; +} + +/** + * API Client for interfacing with the Image Kit API. + */ +export class ImageKit { + privateAPIKey: string; + password: string | null; + + baseURL: string; + maxRetries: number; + timeout: number; + logger: Logger | undefined; + logLevel: LogLevel | undefined; + fetchOptions: MergedRequestInit | undefined; + + private fetch: Fetch; + #encoder: Opts.RequestEncoder; + protected idempotencyHeader?: string; + private _options: ClientOptions; + + /** + * API Client for interfacing with the Image Kit API. + * + * @param {string | undefined} [opts.privateAPIKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] + * @param {string | null | undefined} [opts.password=process.env['ORG_MY_PASSWORD_TOKEN'] ?? does_not_matter] + * @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API. + * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. + * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. + * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. + * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. + * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. + */ + constructor({ + baseURL = readEnv('IMAGE_KIT_BASE_URL'), + privateAPIKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), + password = readEnv('ORG_MY_PASSWORD_TOKEN') ?? 'does_not_matter', + ...opts + }: ClientOptions = {}) { + if (privateAPIKey === undefined) { + throw new Errors.ImageKitError( + "The IMAGEKIT_PRIVATE_API_KEY environment variable is missing or empty; either provide it, or instantiate the ImageKit client with an privateAPIKey option, like new ImageKit({ privateAPIKey: 'My Private API Key' }).", + ); + } + + const options: ClientOptions = { + privateAPIKey, + password, + ...opts, + baseURL: baseURL || `https://api.imagekit.io`, + }; + + this.baseURL = options.baseURL!; + this.timeout = options.timeout ?? ImageKit.DEFAULT_TIMEOUT /* 1 minute */; + this.logger = options.logger ?? console; + const defaultLogLevel = 'warn'; + // Set default logLevel early so that we can log a warning in parseLogLevel. + this.logLevel = defaultLogLevel; + this.logLevel = + parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? + parseLogLevel(readEnv('IMAGE_KIT_LOG'), "process.env['IMAGE_KIT_LOG']", this) ?? + defaultLogLevel; + this.fetchOptions = options.fetchOptions; + this.maxRetries = options.maxRetries ?? 2; + this.fetch = options.fetch ?? Shims.getDefaultFetch(); + this.#encoder = Opts.FallbackEncoder; + + this._options = options; + + this.privateAPIKey = privateAPIKey; + this.password = password; + } + + /** + * Create a new client instance re-using the same options given to the current client with optional overriding. + */ + withOptions(options: Partial): this { + const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ + ...this._options, + baseURL: this.baseURL, + maxRetries: this.maxRetries, + timeout: this.timeout, + logger: this.logger, + logLevel: this.logLevel, + fetch: this.fetch, + fetchOptions: this.fetchOptions, + privateAPIKey: this.privateAPIKey, + password: this.password, + ...options, + }); + return client; + } + + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== 'https://api.imagekit.io'; + } + + protected defaultQuery(): Record | undefined { + return this._options.defaultQuery; + } + + protected validateHeaders({ values, nulls }: NullableHeaders) { + if (this.privateAPIKey && this.password && values.get('authorization')) { + return; + } + if (nulls.has('authorization')) { + return; + } + + throw new Error( + 'Could not resolve authentication method. Expected the privateAPIKey or password to be set. Or for the "Authorization" headers to be explicitly omitted', + ); + } + + protected async authHeaders(opts: FinalRequestOptions): Promise { + if (!this.privateAPIKey) { + return undefined; + } + + if (!this.password) { + return undefined; + } + + const credentials = `${this.privateAPIKey}:${this.password}`; + const Authorization = `Basic ${toBase64(credentials)}`; + return buildHeaders([{ Authorization }]); + } + + /** + * Basic re-implementation of `qs.stringify` for primitive types. + */ + protected stringifyQuery(query: Record): string { + return Object.entries(query) + .filter(([_, value]) => typeof value !== 'undefined') + .map(([key, value]) => { + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + } + if (value === null) { + return `${encodeURIComponent(key)}=`; + } + throw new Errors.ImageKitError( + `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, + ); + }) + .join('&'); + } + + private getUserAgent(): string { + return `${this.constructor.name}/JS ${VERSION}`; + } + + protected defaultIdempotencyKey(): string { + return `stainless-node-retry-${uuid4()}`; + } + + protected makeStatusError( + status: number, + error: Object, + message: string | undefined, + headers: Headers, + ): Errors.APIError { + return Errors.APIError.generate(status, error, message, headers); + } + + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; + const url = + isAbsoluteURL(path) ? + new URL(path) + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + + const defaultQuery = this.defaultQuery(); + if (!isEmptyObj(defaultQuery)) { + query = { ...defaultQuery, ...query }; + } + + if (typeof query === 'object' && query && !Array.isArray(query)) { + url.search = this.stringifyQuery(query as Record); + } + + return url.toString(); + } + + /** + * Used as a callback for mutating the given `FinalRequestOptions` object. + */ + protected async prepareOptions(options: FinalRequestOptions): Promise {} + + /** + * Used as a callback for mutating the given `RequestInit` object. + * + * This is useful for cases where you want to add certain headers based off of + * the request properties, e.g. `method` or `url`. + */ + protected async prepareRequest( + request: RequestInit, + { url, options }: { url: string; options: FinalRequestOptions }, + ): Promise {} + + get(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('get', path, opts); + } + + post(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('post', path, opts); + } + + patch(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('patch', path, opts); + } + + put(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('put', path, opts); + } + + delete(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('delete', path, opts); + } + + private methodRequest( + method: HTTPMethod, + path: string, + opts?: PromiseOrValue, + ): APIPromise { + return this.request( + Promise.resolve(opts).then((opts) => { + return { method, path, ...opts }; + }), + ); + } + + request( + options: PromiseOrValue, + remainingRetries: number | null = null, + ): APIPromise { + return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); + } + + private async makeRequest( + optionsInput: PromiseOrValue, + retriesRemaining: number | null, + retryOfRequestLogID: string | undefined, + ): Promise { + const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; + if (retriesRemaining == null) { + retriesRemaining = maxRetries; + } + + await this.prepareOptions(options); + + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); + + await this.prepareRequest(req, { url, options }); + + /** Not an API request ID, just for correlating local log entries. */ + const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); + const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; + const startTime = Date.now(); + + loggerFor(this).debug( + `[${requestLogID}] sending request`, + formatRequestDetails({ + retryOfRequestLogID, + method: options.method, + url, + options, + headers: req.headers, + }), + ); + + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + + const controller = new AbortController(); + const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); + const headersTime = Date.now(); + + if (response instanceof globalThis.Error) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + // detect native connection timeout errors + // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" + // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" + // others do not provide enough information to distinguish timeouts from other connection errors + const isTimeout = + isAbortError(response) || + /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); + if (retriesRemaining) { + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); + } + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + if (isTimeout) { + throw new Errors.APIConnectionTimeoutError(); + } + throw new Errors.APIConnectionError({ cause: response }); + } + + const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ + response.ok ? 'succeeded' : 'failed' + } with status ${response.status} in ${headersTime - startTime}ms`; + + if (!response.ok) { + const shouldRetry = await this.shouldRetry(response); + if (retriesRemaining && shouldRetry) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + + // We don't need the body of this response. + await Shims.CancelReadableStream(response.body); + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + return this.retryRequest( + options, + retriesRemaining, + retryOfRequestLogID ?? requestLogID, + response.headers, + ); + } + + const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; + + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + + const errText = await response.text().catch((err: any) => castToError(err).message); + const errJSON = safeJSON(errText); + const errMessage = errJSON ? undefined : errText; + + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + message: errMessage, + durationMs: Date.now() - startTime, + }), + ); + + const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); + throw err; + } + + loggerFor(this).info(responseInfo); + loggerFor(this).debug( + `[${requestLogID}] response start`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + + return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; + } + + async fetchWithTimeout( + url: RequestInfo, + init: RequestInit | undefined, + ms: number, + controller: AbortController, + ): Promise { + const { signal, method, ...options } = init || {}; + if (signal) signal.addEventListener('abort', () => controller.abort()); + + const timeout = setTimeout(() => controller.abort(), ms); + + const isReadableBody = + ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || + (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); + + const fetchOptions: RequestInit = { + signal: controller.signal as any, + ...(isReadableBody ? { duplex: 'half' } : {}), + method: 'GET', + ...options, + }; + if (method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = method.toUpperCase(); + } + + try { + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + return await this.fetch.call(undefined, url, fetchOptions); + } finally { + clearTimeout(timeout); + } + } + + private async shouldRetry(response: Response): Promise { + // Note this is not a standard header. + const shouldRetryHeader = response.headers.get('x-should-retry'); + + // If the server explicitly says whether or not to retry, obey. + if (shouldRetryHeader === 'true') return true; + if (shouldRetryHeader === 'false') return false; + + // Retry on request timeouts. + if (response.status === 408) return true; + + // Retry on lock timeouts. + if (response.status === 409) return true; + + // Retry on rate limits. + if (response.status === 429) return true; + + // Retry internal errors. + if (response.status >= 500) return true; + + return false; + } + + private async retryRequest( + options: FinalRequestOptions, + retriesRemaining: number, + requestLogID: string, + responseHeaders?: Headers | undefined, + ): Promise { + let timeoutMillis: number | undefined; + + // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. + const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); + if (retryAfterMillisHeader) { + const timeoutMs = parseFloat(retryAfterMillisHeader); + if (!Number.isNaN(timeoutMs)) { + timeoutMillis = timeoutMs; + } + } + + // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + const retryAfterHeader = responseHeaders?.get('retry-after'); + if (retryAfterHeader && !timeoutMillis) { + const timeoutSeconds = parseFloat(retryAfterHeader); + if (!Number.isNaN(timeoutSeconds)) { + timeoutMillis = timeoutSeconds * 1000; + } else { + timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); + } + } + + // If the API asks us to wait a certain amount of time (and it's a reasonable amount), + // just do what it says, but otherwise calculate a default + if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + const maxRetries = options.maxRetries ?? this.maxRetries; + timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); + } + await sleep(timeoutMillis); + + return this.makeRequest(options, retriesRemaining - 1, requestLogID); + } + + private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { + const initialRetryDelay = 0.5; + const maxRetryDelay = 8.0; + + const numRetries = maxRetries - retriesRemaining; + + // Apply exponential backoff, but not more than the max. + const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); + + // Apply some jitter, take up to at most 25 percent of the retry time. + const jitter = 1 - Math.random() * 0.25; + + return sleepSeconds * jitter * 1000; + } + + async buildRequest( + inputOptions: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { + const options = { ...inputOptions }; + const { method, path, query, defaultBaseURL } = options; + + const url = this.buildURL(path!, query as Record, defaultBaseURL); + if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); + options.timeout = options.timeout ?? this.timeout; + const { bodyHeaders, body } = this.buildBody({ options }); + const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); + + const req: FinalizedRequestInit = { + method, + headers: reqHeaders, + ...(options.signal && { signal: options.signal }), + ...((globalThis as any).ReadableStream && + body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), + ...(body && { body }), + ...((this.fetchOptions as any) ?? {}), + ...((options.fetchOptions as any) ?? {}), + }; + + return { req, url, timeout: options.timeout }; + } + + private async buildHeaders({ + options, + method, + bodyHeaders, + retryCount, + }: { + options: FinalRequestOptions; + method: HTTPMethod; + bodyHeaders: HeadersLike; + retryCount: number; + }): Promise { + let idempotencyHeaders: HeadersLike = {}; + if (this.idempotencyHeader && method !== 'get') { + if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); + idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; + } + + const headers = buildHeaders([ + idempotencyHeaders, + { + Accept: 'application/json', + 'User-Agent': this.getUserAgent(), + 'X-Stainless-Retry-Count': String(retryCount), + ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), + ...getPlatformHeaders(), + }, + await this.authHeaders(options), + this._options.defaultHeaders, + bodyHeaders, + options.headers, + ]); + + this.validateHeaders(headers); + + return headers.values; + } + + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + bodyHeaders: HeadersLike; + body: BodyInit | undefined; + } { + if (!body) { + return { bodyHeaders: undefined, body: undefined }; + } + const headers = buildHeaders([rawHeaders]); + if ( + // Pass raw type verbatim + ArrayBuffer.isView(body) || + body instanceof ArrayBuffer || + body instanceof DataView || + (typeof body === 'string' && + // Preserve legacy string encoding behavior for now + headers.values.has('content-type')) || + // `Blob` is superset of `File` + ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || + // `FormData` -> `multipart/form-data` + body instanceof FormData || + // `URLSearchParams` -> `application/x-www-form-urlencoded` + body instanceof URLSearchParams || + // Send chunked stream (each chunk has own `length`) + ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) + ) { + return { bodyHeaders: undefined, body: body as BodyInit }; + } else if ( + typeof body === 'object' && + (Symbol.asyncIterator in body || + (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) + ) { + return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else { + return this.#encoder({ body, headers }); + } + } + + static ImageKit = this; + static DEFAULT_TIMEOUT = 60000; // 1 minute + + static ImageKitError = Errors.ImageKitError; + static APIError = Errors.APIError; + static APIConnectionError = Errors.APIConnectionError; + static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; + static APIUserAbortError = Errors.APIUserAbortError; + static NotFoundError = Errors.NotFoundError; + static ConflictError = Errors.ConflictError; + static RateLimitError = Errors.RateLimitError; + static BadRequestError = Errors.BadRequestError; + static AuthenticationError = Errors.AuthenticationError; + static InternalServerError = Errors.InternalServerError; + static PermissionDeniedError = Errors.PermissionDeniedError; + static UnprocessableEntityError = Errors.UnprocessableEntityError; + + static toFile = Uploads.toFile; + + customMetadataFields: API.CustomMetadataFields = new API.CustomMetadataFields(this); + files: API.Files = new API.Files(this); + assets: API.Assets = new API.Assets(this); + cache: API.Cache = new API.Cache(this); + folders: API.Folders = new API.Folders(this); + accounts: API.Accounts = new API.Accounts(this); + beta: API.Beta = new API.Beta(this); + webhooks: API.Webhooks = new API.Webhooks(this); +} + +ImageKit.CustomMetadataFields = CustomMetadataFields; +ImageKit.Files = Files; +ImageKit.Assets = Assets; +ImageKit.Cache = Cache; +ImageKit.Folders = Folders; +ImageKit.Accounts = Accounts; +ImageKit.Beta = Beta; +ImageKit.Webhooks = Webhooks; + +export declare namespace ImageKit { + export type RequestOptions = Opts.RequestOptions; + + export { + CustomMetadataFields as CustomMetadataFields, + type CustomMetadataField as CustomMetadataField, + type CustomMetadataFieldListResponse as CustomMetadataFieldListResponse, + type CustomMetadataFieldDeleteResponse as CustomMetadataFieldDeleteResponse, + type CustomMetadataFieldCreateParams as CustomMetadataFieldCreateParams, + type CustomMetadataFieldUpdateParams as CustomMetadataFieldUpdateParams, + type CustomMetadataFieldListParams as CustomMetadataFieldListParams, + }; + + export { + Files as Files, + type File as File, + type Folder as Folder, + type Metadata as Metadata, + type FileUpdateResponse as FileUpdateResponse, + type FileCopyResponse as FileCopyResponse, + type FileMoveResponse as FileMoveResponse, + type FileRenameResponse as FileRenameResponse, + type FileUploadResponse as FileUploadResponse, + type FileUpdateParams as FileUpdateParams, + type FileCopyParams as FileCopyParams, + type FileMoveParams as FileMoveParams, + type FileRenameParams as FileRenameParams, + type FileUploadParams as FileUploadParams, + }; + + export { + Assets as Assets, + type AssetListResponse as AssetListResponse, + type AssetListParams as AssetListParams, + }; + + export { Cache as Cache }; + + export { + Folders as Folders, + type FolderCreateResponse as FolderCreateResponse, + type FolderDeleteResponse as FolderDeleteResponse, + type FolderCopyResponse as FolderCopyResponse, + type FolderMoveResponse as FolderMoveResponse, + type FolderRenameResponse as FolderRenameResponse, + type FolderCreateParams as FolderCreateParams, + type FolderDeleteParams as FolderDeleteParams, + type FolderCopyParams as FolderCopyParams, + type FolderMoveParams as FolderMoveParams, + type FolderRenameParams as FolderRenameParams, + }; + + export { Accounts as Accounts }; + + export { Beta as Beta }; + + export { + Webhooks as Webhooks, + type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, + type VideoTransformationErrorEvent as VideoTransformationErrorEvent, + type VideoTransformationReadyEvent as VideoTransformationReadyEvent, + type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, + type UnwrapWebhookEvent as UnwrapWebhookEvent, + }; + + export type BaseOverlay = API.BaseOverlay; + export type ImageOverlay = API.ImageOverlay; + export type Overlay = API.Overlay; + export type OverlayPosition = API.OverlayPosition; + export type OverlayTiming = API.OverlayTiming; + export type SolidColorOverlay = API.SolidColorOverlay; + export type SolidColorOverlayTransformation = API.SolidColorOverlayTransformation; + export type SrcOptions = API.SrcOptions; + export type StreamingResolution = API.StreamingResolution; + export type SubtitleOverlay = API.SubtitleOverlay; + export type SubtitleOverlayTransformation = API.SubtitleOverlayTransformation; + export type TextOverlay = API.TextOverlay; + export type TextOverlayTransformation = API.TextOverlayTransformation; + export type Transformation = API.Transformation; + export type TransformationPosition = API.TransformationPosition; + export type VideoOverlay = API.VideoOverlay; +} diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 00000000..485fce86 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,3 @@ +# `core` + +This directory holds public modules implementing non-resource-specific SDK functionality. diff --git a/src/core/api-promise.ts b/src/core/api-promise.ts new file mode 100644 index 00000000..53821f14 --- /dev/null +++ b/src/core/api-promise.ts @@ -0,0 +1,92 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { type ImageKit } from '../client'; + +import { type PromiseOrValue } from '../internal/types'; +import { APIResponseProps, defaultParseResponse } from '../internal/parse'; + +/** + * A subclass of `Promise` providing additional helper methods + * for interacting with the SDK. + */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + #client: ImageKit; + + constructor( + client: ImageKit, + private responsePromise: Promise, + private parseResponse: ( + client: ImageKit, + props: APIResponseProps, + ) => PromiseOrValue = defaultParseResponse, + ) { + super((resolve) => { + // this is maybe a bit weird but this has to be a no-op to not implicitly + // parse the response body; instead .then, .catch, .finally are overridden + // to parse the response + resolve(null as any); + }); + this.#client = client; + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.#client, this.responsePromise, async (client, props) => + transform(await this.parseResponse(client, props), props), + ); + } + + /** + * Gets the raw `Response` instance instead of parsing the response + * data. + * + * If you want to parse the response body but still get the `Response` + * instance, you can use {@link withResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + asResponse(): Promise { + return this.responsePromise.then((p) => p.response); + } + + /** + * Gets the parsed response data and the raw `Response` instance. + * + * If you just want to get the raw `Response` instance without parsing it, + * you can use {@link asResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/core/error.ts b/src/core/error.ts new file mode 100644 index 00000000..47d1cc24 --- /dev/null +++ b/src/core/error.ts @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { castToError } from '../internal/errors'; + +export class ImageKitError extends Error {} + +export class APIError< + TStatus extends number | undefined = number | undefined, + THeaders extends Headers | undefined = Headers | undefined, + TError extends Object | undefined = Object | undefined, +> extends ImageKitError { + /** HTTP status for the response that caused the error */ + readonly status: TStatus; + /** HTTP headers for the response that caused the error */ + readonly headers: THeaders; + /** JSON body of the response that caused the error */ + readonly error: TError; + + constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { + super(`${APIError.makeMessage(status, error, message)}`); + this.status = status; + this.headers = headers; + this.error = error; + } + + private static makeMessage(status: number | undefined, error: any, message: string | undefined) { + const msg = + error?.message ? + typeof error.message === 'string' ? + error.message + : JSON.stringify(error.message) + : error ? JSON.stringify(error) + : message; + + if (status && msg) { + return `${status} ${msg}`; + } + if (status) { + return `${status} status code (no body)`; + } + if (msg) { + return msg; + } + return '(no status code or body)'; + } + + static generate( + status: number | undefined, + errorResponse: Object | undefined, + message: string | undefined, + headers: Headers | undefined, + ): APIError { + if (!status || !headers) { + return new APIConnectionError({ message, cause: castToError(errorResponse) }); + } + + const error = errorResponse as Record; + + if (status === 400) { + return new BadRequestError(status, error, message, headers); + } + + if (status === 401) { + return new AuthenticationError(status, error, message, headers); + } + + if (status === 403) { + return new PermissionDeniedError(status, error, message, headers); + } + + if (status === 404) { + return new NotFoundError(status, error, message, headers); + } + + if (status === 409) { + return new ConflictError(status, error, message, headers); + } + + if (status === 422) { + return new UnprocessableEntityError(status, error, message, headers); + } + + if (status === 429) { + return new RateLimitError(status, error, message, headers); + } + + if (status >= 500) { + return new InternalServerError(status, error, message, headers); + } + + return new APIError(status, error, message, headers); + } +} + +export class APIUserAbortError extends APIError { + constructor({ message }: { message?: string } = {}) { + super(undefined, undefined, message || 'Request was aborted.', undefined); + } +} + +export class APIConnectionError extends APIError { + constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { + super(undefined, undefined, message || 'Connection error.', undefined); + // in some environments the 'cause' property is already declared + // @ts-ignore + if (cause) this.cause = cause; + } +} + +export class APIConnectionTimeoutError extends APIConnectionError { + constructor({ message }: { message?: string } = {}) { + super({ message: message ?? 'Request timed out.' }); + } +} + +export class BadRequestError extends APIError<400, Headers> {} + +export class AuthenticationError extends APIError<401, Headers> {} + +export class PermissionDeniedError extends APIError<403, Headers> {} + +export class NotFoundError extends APIError<404, Headers> {} + +export class ConflictError extends APIError<409, Headers> {} + +export class UnprocessableEntityError extends APIError<422, Headers> {} + +export class RateLimitError extends APIError<429, Headers> {} + +export class InternalServerError extends APIError {} diff --git a/src/core/resource.ts b/src/core/resource.ts new file mode 100644 index 00000000..51525101 --- /dev/null +++ b/src/core/resource.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { ImageKit } from '../client'; + +export abstract class APIResource { + protected _client: ImageKit; + + constructor(client: ImageKit) { + this._client = client; + } +} diff --git a/src/core/uploads.ts b/src/core/uploads.ts new file mode 100644 index 00000000..2882ca6d --- /dev/null +++ b/src/core/uploads.ts @@ -0,0 +1,2 @@ +export { type Uploadable } from '../internal/uploads'; +export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..fc55f46c --- /dev/null +++ b/src/error.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/error instead */ +export * from './core/error'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..71734d88 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { ImageKit as default } from './client'; + +export { type Uploadable, toFile } from './core/uploads'; +export { APIPromise } from './core/api-promise'; +export { ImageKit, type ClientOptions } from './client'; +export { + ImageKitError, + APIError, + APIConnectionError, + APIConnectionTimeoutError, + APIUserAbortError, + NotFoundError, + ConflictError, + RateLimitError, + BadRequestError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, +} from './core/error'; diff --git a/src/internal/README.md b/src/internal/README.md new file mode 100644 index 00000000..3ef5a25b --- /dev/null +++ b/src/internal/README.md @@ -0,0 +1,3 @@ +# `internal` + +The modules in this directory are not importable outside this package and will change between releases. diff --git a/src/internal/builtin-types.ts b/src/internal/builtin-types.ts new file mode 100644 index 00000000..c23d3bde --- /dev/null +++ b/src/internal/builtin-types.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; + +/** + * An alias to the builtin `RequestInit` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit + */ +type _RequestInit = RequestInit; + +/** + * An alias to the builtin `Response` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/Response + */ +type _Response = Response; + +/** + * The type for the first argument to `fetch`. + * + * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource + */ +type _RequestInfo = Request | URL | string; + +/** + * The type for constructing `RequestInit` Headers. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers + */ +type _HeadersInit = RequestInit['headers']; + +/** + * The type for constructing `RequestInit` body. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#body + */ +type _BodyInit = RequestInit['body']; + +/** + * An alias to the builtin `Array` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Array = Array; + +/** + * An alias to the builtin `Record` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Record = Record; + +export type { + _Array as Array, + _BodyInit as BodyInit, + _HeadersInit as HeadersInit, + _Record as Record, + _RequestInfo as RequestInfo, + _RequestInit as RequestInit, + _Response as Response, +}; + +/** + * A copy of the builtin `EndingType` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 + */ +type EndingType = 'native' | 'transparent'; + +/** + * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 + * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options + */ +export interface BlobPropertyBag { + endings?: EndingType; + type?: string; +} + +/** + * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 + * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options + */ +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} diff --git a/src/internal/detect-platform.ts b/src/internal/detect-platform.ts new file mode 100644 index 00000000..e82d95c9 --- /dev/null +++ b/src/internal/detect-platform.ts @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { VERSION } from '../version'; + +export const isRunningInBrowser = () => { + return ( + // @ts-ignore + typeof window !== 'undefined' && + // @ts-ignore + typeof window.document !== 'undefined' && + // @ts-ignore + typeof navigator !== 'undefined' + ); +}; + +type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; + +/** + * Note this does not detect 'browser'; for that, use getBrowserInfo(). + */ +function getDetectedPlatform(): DetectedPlatform { + if (typeof Deno !== 'undefined' && Deno.build != null) { + return 'deno'; + } + if (typeof EdgeRuntime !== 'undefined') { + return 'edge'; + } + if ( + Object.prototype.toString.call( + typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, + ) === '[object process]' + ) { + return 'node'; + } + return 'unknown'; +} + +declare const Deno: any; +declare const EdgeRuntime: any; +type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; +type PlatformName = + | 'MacOS' + | 'Linux' + | 'Windows' + | 'FreeBSD' + | 'OpenBSD' + | 'iOS' + | 'Android' + | `Other:${string}` + | 'Unknown'; +type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; +type PlatformProperties = { + 'X-Stainless-Lang': 'js'; + 'X-Stainless-Package-Version': string; + 'X-Stainless-OS': PlatformName; + 'X-Stainless-Arch': Arch; + 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; + 'X-Stainless-Runtime-Version': string; +}; +const getPlatformProperties = (): PlatformProperties => { + const detectedPlatform = getDetectedPlatform(); + if (detectedPlatform === 'deno') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform(Deno.build.os), + 'X-Stainless-Arch': normalizeArch(Deno.build.arch), + 'X-Stainless-Runtime': 'deno', + 'X-Stainless-Runtime-Version': + typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', + }; + } + if (typeof EdgeRuntime !== 'undefined') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': `other:${EdgeRuntime}`, + 'X-Stainless-Runtime': 'edge', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version, + }; + } + // Check if Node.js + if (detectedPlatform === 'node') { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), + 'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), + 'X-Stainless-Runtime': 'node', + 'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown', + }; + } + + const browserInfo = getBrowserInfo(); + if (browserInfo) { + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, + 'X-Stainless-Runtime-Version': browserInfo.version, + }; + } + + // TODO add support for Cloudflare workers, etc. + return { + 'X-Stainless-Lang': 'js', + 'X-Stainless-Package-Version': VERSION, + 'X-Stainless-OS': 'Unknown', + 'X-Stainless-Arch': 'unknown', + 'X-Stainless-Runtime': 'unknown', + 'X-Stainless-Runtime-Version': 'unknown', + }; +}; + +type BrowserInfo = { + browser: Browser; + version: string; +}; + +declare const navigator: { userAgent: string } | undefined; + +// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts +function getBrowserInfo(): BrowserInfo | null { + if (typeof navigator === 'undefined' || !navigator) { + return null; + } + + // NOTE: The order matters here! + const browserPatterns = [ + { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, + ]; + + // Find the FIRST matching browser + for (const { key, pattern } of browserPatterns) { + const match = pattern.exec(navigator.userAgent); + if (match) { + const major = match[1] || 0; + const minor = match[2] || 0; + const patch = match[3] || 0; + + return { browser: key, version: `${major}.${minor}.${patch}` }; + } + } + + return null; +} + +const normalizeArch = (arch: string): Arch => { + // Node docs: + // - https://nodejs.org/api/process.html#processarch + // Deno docs: + // - https://doc.deno.land/deno/stable/~/Deno.build + if (arch === 'x32') return 'x32'; + if (arch === 'x86_64' || arch === 'x64') return 'x64'; + if (arch === 'arm') return 'arm'; + if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; + if (arch) return `other:${arch}`; + return 'unknown'; +}; + +const normalizePlatform = (platform: string): PlatformName => { + // Node platforms: + // - https://nodejs.org/api/process.html#processplatform + // Deno platforms: + // - https://doc.deno.land/deno/stable/~/Deno.build + // - https://github.com/denoland/deno/issues/14799 + + platform = platform.toLowerCase(); + + // NOTE: this iOS check is untested and may not work + // Node does not work natively on IOS, there is a fork at + // https://github.com/nodejs-mobile/nodejs-mobile + // however it is unknown at the time of writing how to detect if it is running + if (platform.includes('ios')) return 'iOS'; + if (platform === 'android') return 'Android'; + if (platform === 'darwin') return 'MacOS'; + if (platform === 'win32') return 'Windows'; + if (platform === 'freebsd') return 'FreeBSD'; + if (platform === 'openbsd') return 'OpenBSD'; + if (platform === 'linux') return 'Linux'; + if (platform) return `Other:${platform}`; + return 'Unknown'; +}; + +let _platformHeaders: PlatformProperties; +export const getPlatformHeaders = () => { + return (_platformHeaders ??= getPlatformProperties()); +}; diff --git a/src/internal/errors.ts b/src/internal/errors.ts new file mode 100644 index 00000000..82c7b14d --- /dev/null +++ b/src/internal/errors.ts @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export function isAbortError(err: unknown) { + return ( + typeof err === 'object' && + err !== null && + // Spec-compliant fetch implementations + (('name' in err && (err as any).name === 'AbortError') || + // Expo fetch + ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) + ); +} + +export const castToError = (err: any): Error => { + if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + if (Object.prototype.toString.call(err) === '[object Error]') { + // @ts-ignore - not all envs have native support for cause yet + const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); + if (err.stack) error.stack = err.stack; + // @ts-ignore - not all envs have native support for cause yet + if (err.cause && !error.cause) error.cause = err.cause; + if (err.name) error.name = err.name; + return error; + } + } catch {} + try { + return new Error(JSON.stringify(err)); + } catch {} + } + return new Error(err); +}; diff --git a/src/internal/headers.ts b/src/internal/headers.ts new file mode 100644 index 00000000..c724a9d2 --- /dev/null +++ b/src/internal/headers.ts @@ -0,0 +1,97 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { isReadonlyArray } from './utils/values'; + +type HeaderValue = string | undefined | null; +export type HeadersLike = + | Headers + | readonly HeaderValue[][] + | Record + | undefined + | null + | NullableHeaders; + +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); + +/** + * @internal + * Users can pass explicit nulls to unset default headers. When we parse them + * into a standard headers type we need to preserve that information. + */ +export type NullableHeaders = { + /** Brand check, prevent users from creating a NullableHeaders. */ + [brand_privateNullableHeaders]: true; + /** Parsed headers. */ + values: Headers; + /** Set of lowercase header names explicitly set to null. */ + nulls: Set; +}; + +function* iterateHeaders(headers: HeadersLike): IterableIterator { + if (!headers) return; + + if (brand_privateNullableHeaders in headers) { + const { values, nulls } = headers; + yield* values.entries(); + for (const name of nulls) { + yield [name, null]; + } + return; + } + + let shouldClear = false; + let iter: Iterable; + if (headers instanceof Headers) { + iter = headers.entries(); + } else if (isReadonlyArray(headers)) { + iter = headers; + } else { + shouldClear = true; + iter = Object.entries(headers ?? {}); + } + for (let row of iter) { + const name = row[0]; + if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); + const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; + let didClear = false; + for (const value of values) { + if (value === undefined) continue; + + // Objects keys always overwrite older headers, they never append. + // Yield a null to clear the header before adding the new values. + if (shouldClear && !didClear) { + didClear = true; + yield [name, null]; + } + yield [name, value]; + } + } +} + +export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { + const targetHeaders = new Headers(); + const nullHeaders = new Set(); + for (const headers of newHeaders) { + const seenHeaders = new Set(); + for (const [name, value] of iterateHeaders(headers)) { + const lowerName = name.toLowerCase(); + if (!seenHeaders.has(lowerName)) { + targetHeaders.delete(name); + seenHeaders.add(lowerName); + } + if (value === null) { + targetHeaders.delete(name); + nullHeaders.add(lowerName); + } else { + targetHeaders.append(name, value); + nullHeaders.delete(lowerName); + } + } + } + return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; +}; + +export const isEmptyHeaders = (headers: HeadersLike) => { + for (const _ of iterateHeaders(headers)) return false; + return true; +}; diff --git a/src/internal/parse.ts b/src/internal/parse.ts new file mode 100644 index 00000000..10d1ca90 --- /dev/null +++ b/src/internal/parse.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import type { FinalRequestOptions } from './request-options'; +import { type ImageKit } from '../client'; +import { formatRequestDetails, loggerFor } from './utils/log'; + +export type APIResponseProps = { + response: Response; + options: FinalRequestOptions; + controller: AbortController; + requestLogID: string; + retryOfRequestLogID: string | undefined; + startTime: number; +}; + +export async function defaultParseResponse(client: ImageKit, props: APIResponseProps): Promise { + const { response, requestLogID, retryOfRequestLogID, startTime } = props; + const body = await (async () => { + // fetch refuses to read the body when the status code is 204. + if (response.status === 204) { + return null as T; + } + + if (props.options.__binaryResponse) { + return response as unknown as T; + } + + const contentType = response.headers.get('content-type'); + const mediaType = contentType?.split(';')[0]?.trim(); + const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); + if (isJSON) { + const json = await response.json(); + return json as T; + } + + const text = await response.text(); + return text as unknown as T; + })(); + loggerFor(client).debug( + `[${requestLogID}] response parsed`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + body, + durationMs: Date.now() - startTime, + }), + ); + return body; +} diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts new file mode 100644 index 00000000..2aabf9aa --- /dev/null +++ b/src/internal/request-options.ts @@ -0,0 +1,91 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { NullableHeaders } from './headers'; + +import type { BodyInit } from './builtin-types'; +import type { HTTPMethod, MergedRequestInit } from './types'; +import { type HeadersLike } from './headers'; + +export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; + +export type RequestOptions = { + /** + * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). + */ + method?: HTTPMethod; + + /** + * The URL path for the request. + * + * @example "/v1/foo" + */ + path?: string; + + /** + * Query parameters to include in the request URL. + */ + query?: object | undefined | null; + + /** + * The request body. Can be a string, JSON object, FormData, or other supported types. + */ + body?: unknown; + + /** + * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. + */ + headers?: HeadersLike; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number; + + stream?: boolean | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * @unit milliseconds + */ + timeout?: number; + + /** + * Additional `RequestInit` options to be passed to the underlying `fetch` call. + * These options will be merged with the client's default fetch options. + */ + fetchOptions?: MergedRequestInit; + + /** + * An AbortSignal that can be used to cancel the request. + */ + signal?: AbortSignal | undefined | null; + + /** + * A unique key for this request to enable idempotency. + */ + idempotencyKey?: string; + + /** + * Override the default base URL for this specific request. + */ + defaultBaseURL?: string | undefined; + + __binaryResponse?: boolean | undefined; +}; + +export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; +export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; + +export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { + return { + bodyHeaders: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; diff --git a/src/internal/shim-types.ts b/src/internal/shim-types.ts new file mode 100644 index 00000000..8ddf7b0a --- /dev/null +++ b/src/internal/shim-types.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +type NeverToAny = T extends never ? any : T; + +/** @ts-ignore */ +type _DOMReadableStream = globalThis.ReadableStream; + +/** @ts-ignore */ +type _NodeReadableStream = import('stream/web').ReadableStream; + +type _ConditionalNodeReadableStream = + typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; + +type _ReadableStream = NeverToAny< + | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) + | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) +>; + +export type { _ReadableStream as ReadableStream }; diff --git a/src/internal/shims.ts b/src/internal/shims.ts new file mode 100644 index 00000000..e4dc7102 --- /dev/null +++ b/src/internal/shims.ts @@ -0,0 +1,107 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. + * + * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error + * messages in cases where an environment isn't fully supported. + */ + +import type { Fetch } from './builtin-types'; +import type { ReadableStream } from './shim-types'; + +export function getDefaultFetch(): Fetch { + if (typeof fetch !== 'undefined') { + return fetch as any; + } + + throw new Error( + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new ImageKit({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + ); +} + +type ReadableStreamArgs = ConstructorParameters; + +export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { + const ReadableStream = (globalThis as any).ReadableStream; + if (typeof ReadableStream === 'undefined') { + // Note: All of the platforms / runtimes we officially support already define + // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. + throw new Error( + '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', + ); + } + + return new ReadableStream(...args); +} + +export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { + let iter: AsyncIterator | Iterator = + Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); + + return makeReadableStream({ + start() {}, + async pull(controller: any) { + const { done, value } = await iter.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel() { + await iter.return?.(); + }, + }); +} + +/** + * Most browsers don't yet have async iterable support for ReadableStream, + * and Node has a very different way of reading bytes from its "ReadableStream". + * + * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 + */ +export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { + if (stream[Symbol.asyncIterator]) return stream; + + const reader = stream.getReader(); + return { + async next() { + try { + const result = await reader.read(); + if (result?.done) reader.releaseLock(); // release lock when stream becomes closed + return result; + } catch (e) { + reader.releaseLock(); // release lock when stream becomes errored + throw e; + } + }, + async return() { + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; + return { done: true, value: undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; +} + +/** + * Cancels a ReadableStream we don't need to consume. + * See https://undici.nodejs.org/#/?id=garbage-collection + */ +export async function CancelReadableStream(stream: any): Promise { + if (stream === null || typeof stream !== 'object') return; + + if (stream[Symbol.asyncIterator]) { + await stream[Symbol.asyncIterator]().return?.(); + return; + } + + const reader = stream.getReader(); + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; +} diff --git a/src/internal/to-file.ts b/src/internal/to-file.ts new file mode 100644 index 00000000..245e8493 --- /dev/null +++ b/src/internal/to-file.ts @@ -0,0 +1,154 @@ +import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; +import type { FilePropertyBag } from './builtin-types'; +import { checkFileSupport } from './uploads'; + +type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; + +/** + * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. + * Don't add arrayBuffer here, node-fetch doesn't have it + */ +interface BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + readonly size: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + readonly type: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number): BlobLike; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function' && + typeof value.slice === 'function' && + typeof value.arrayBuffer === 'function'; + +/** + * Intended to match DOM File, node:buffer File, undici File, etc. + */ +interface FileLike extends BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + readonly lastModified: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + readonly name?: string | undefined; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.name === 'string' && + typeof value.lastModified === 'number' && + isBlobLike(value); + +/** + * Intended to match DOM Response, node-fetch Response, undici Response, etc. + */ +export interface ResponseLike { + url: string; + blob(): Promise; +} + +const isResponseLike = (value: any): value is ResponseLike => + value != null && + typeof value === 'object' && + typeof value.url === 'string' && + typeof value.blob === 'function'; + +export type ToFileInput = + | FileLike + | ResponseLike + | Exclude + | AsyncIterable; + +/** + * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats + * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s + * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible + * @param {Object=} options additional properties + * @param {string=} options.type the MIME type of the content + * @param {number=} options.lastModified the last modified timestamp + * @returns a {@link File} with the given properties + */ +export async function toFile( + value: ToFileInput | PromiseLike, + name?: string | null | undefined, + options?: FilePropertyBag | undefined, +): Promise { + checkFileSupport(); + + // If it's a promise, resolve it. + value = await value; + + // If we've been given a `File` we don't need to do anything + if (isFileLike(value)) { + if (value instanceof File) { + return value; + } + return makeFile([await value.arrayBuffer()], value.name); + } + + if (isResponseLike(value)) { + const blob = await value.blob(); + name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); + + return makeFile(await getBytes(blob), name, options); + } + + const parts = await getBytes(value); + + name ||= getName(value); + + if (!options?.type) { + const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); + if (typeof type === 'string') { + options = { ...options, type }; + } + } + + return makeFile(parts, name, options); +} + +async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { + let parts: Array = []; + if ( + typeof value === 'string' || + ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. + value instanceof ArrayBuffer + ) { + parts.push(value); + } else if (isBlobLike(value)) { + parts.push(value instanceof Blob ? value : await value.arrayBuffer()); + } else if ( + isAsyncIterable(value) // includes Readable, ReadableStream, etc. + ) { + for await (const chunk of value) { + parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? + } + } else { + const constructor = value?.constructor?.name; + throw new Error( + `Unexpected data type: ${typeof value}${ + constructor ? `; constructor: ${constructor}` : '' + }${propsForError(value)}`, + ); + } + + return parts; +} + +function propsForError(value: unknown): string { + if (typeof value !== 'object' || value === null) return ''; + const props = Object.getOwnPropertyNames(value); + return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; +} diff --git a/src/internal/types.ts b/src/internal/types.ts new file mode 100644 index 00000000..b668dfc0 --- /dev/null +++ b/src/internal/types.ts @@ -0,0 +1,95 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export type PromiseOrValue = T | Promise; +export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +export type KeysEnum = { [P in keyof Required]: true }; + +export type FinalizedRequestInit = RequestInit & { headers: Headers }; + +type NotAny = [0] extends [1 & T] ? never : T; + +/** + * Some environments overload the global fetch function, and Parameters only gets the last signature. + */ +type OverloadedParameters = + T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + (...args: infer D): unknown; + } + ) ? + A | B | C | D + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + } + ) ? + A | B | C + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + } + ) ? + A | B + : T extends (...args: infer A) => unknown ? A + : never; + +/* eslint-disable */ +/** + * These imports attempt to get types from a parent package's dependencies. + * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which + * would cause typescript to show types not present at runtime. To avoid this, we import + * directly from parent node_modules folders. + * + * We need to check multiple levels because we don't know what directory structure we'll be in. + * For example, pnpm generates directories like this: + * ``` + * node_modules + * ├── .pnpm + * │ └── pkg@1.0.0 + * │ └── node_modules + * │ └── pkg + * │ └── internal + * │ └── types.d.ts + * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg + * └── undici + * ``` + * + * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition + */ +/** @ts-ignore For users with \@types/node */ +type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with undici */ +type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with \@types/bun */ +type BunRequestInit = globalThis.FetchRequestInit; +/** @ts-ignore For users with node-fetch@2 */ +type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ +type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users who use Deno */ +type FetchRequestInit = NonNullable[1]>; +/* eslint-enable */ + +type RequestInits = + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny; + +/** + * This type contains `RequestInit` options that may be available on the current runtime, + * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. + */ +export type MergedRequestInit = RequestInits & + /** We don't include these in the types as they'll be overridden for every request. */ + Partial>; diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts new file mode 100644 index 00000000..bfc86b43 --- /dev/null +++ b/src/internal/uploads.ts @@ -0,0 +1,187 @@ +import { type RequestOptions } from './request-options'; +import type { FilePropertyBag, Fetch } from './builtin-types'; +import type { ImageKit } from '../client'; +import { ReadableStreamFrom } from './shims'; + +export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; +type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; + +// https://github.com/oven-sh/bun/issues/5980 +interface BunFile extends Blob { + readonly name?: string | undefined; +} + +export const checkFileSupport = () => { + if (typeof File === 'undefined') { + const { process } = globalThis as any; + const isOldNode = + typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; + throw new Error( + '`File` is not defined as a global, which is required for file uploads.' + + (isOldNode ? + " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." + : ''), + ); + } +}; + +/** + * Typically, this is a native "File" class. + * + * We provide the {@link toFile} utility to convert a variety of objects + * into the File class. + * + * For convenience, you can also pass a fetch Response, or in Node, + * the result of fs.createReadStream(). + */ +export type Uploadable = File | Response | FsReadStream | BunFile; + +/** + * Construct a `File` instance. This is used to ensure a helpful error is thrown + * for environments that don't define a global `File` yet. + */ +export function makeFile( + fileBits: BlobPart[], + fileName: string | undefined, + options?: FilePropertyBag, +): File { + checkFileSupport(); + return new File(fileBits as any, fileName ?? 'unknown_file', options); +} + +export function getName(value: any): string | undefined { + return ( + ( + (typeof value === 'object' && + value !== null && + (('name' in value && value.name && String(value.name)) || + ('url' in value && value.url && String(value.url)) || + ('filename' in value && value.filename && String(value.filename)) || + ('path' in value && value.path && String(value.path)))) || + '' + ) + .split(/[\\/]/) + .pop() || undefined + ); +} + +export const isAsyncIterable = (value: any): value is AsyncIterable => + value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; + +/** + * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. + * Otherwise returns the request as is. + */ +export const maybeMultipartFormRequestOptions = async ( + opts: RequestOptions, + fetch: ImageKit | Fetch, +): Promise => { + if (!hasUploadableValue(opts.body)) return opts; + + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +type MultipartFormRequestOptions = Omit & { body: unknown }; + +export const multipartFormRequestOptions = async ( + opts: MultipartFormRequestOptions, + fetch: ImageKit | Fetch, +): Promise => { + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); + +/** + * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending + * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". + * This function detects if the fetch function provided supports the global FormData object to avoid + * confusing error messages later on. + */ +function supportsFormData(fetchObject: ImageKit | Fetch): Promise { + const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; + const cached = supportsFormDataMap.get(fetch); + if (cached) return cached; + const promise = (async () => { + try { + const FetchResponse = ( + 'Response' in fetch ? + fetch.Response + : (await fetch('data:,')).constructor) as typeof Response; + const data = new FormData(); + if (data.toString() === (await new FetchResponse(data).text())) { + return false; + } + return true; + } catch { + // avoid false negatives + return true; + } + })(); + supportsFormDataMap.set(fetch, promise); + return promise; +} + +export const createForm = async >( + body: T | undefined, + fetch: ImageKit | Fetch, +): Promise => { + if (!(await supportsFormData(fetch))) { + throw new TypeError( + 'The provided fetch function does not support file uploads with the current global FormData class.', + ); + } + const form = new FormData(); + await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); + return form; +}; + +// We check for Blob not File because Bun.File doesn't inherit from File, +// but they both inherit from Blob and have a `name` property at runtime. +const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; + +const isUploadable = (value: unknown) => + typeof value === 'object' && + value !== null && + (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); + +const hasUploadableValue = (value: unknown): boolean => { + if (isUploadable(value)) return true; + if (Array.isArray(value)) return value.some(hasUploadableValue); + if (value && typeof value === 'object') { + for (const k in value) { + if (hasUploadableValue((value as any)[k])) return true; + } + } + return false; +}; + +const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { + if (value === undefined) return; + if (value == null) { + throw new TypeError( + `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, + ); + } + + // TODO: make nested formats configurable + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + form.append(key, String(value)); + } else if (value instanceof Response) { + form.append(key, makeFile([await value.blob()], getName(value))); + } else if (isAsyncIterable(value)) { + form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); + } else if (isNamedBlob(value)) { + form.append(key, value, getName(value)); + } else if (Array.isArray(value)) { + await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); + } else if (typeof value === 'object') { + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), + ); + } else { + throw new TypeError( + `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, + ); + } +}; diff --git a/src/internal/utils.ts b/src/internal/utils.ts new file mode 100644 index 00000000..3cbfacce --- /dev/null +++ b/src/internal/utils.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './utils/values'; +export * from './utils/base64'; +export * from './utils/env'; +export * from './utils/log'; +export * from './utils/uuid'; +export * from './utils/sleep'; diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts new file mode 100644 index 00000000..2312df37 --- /dev/null +++ b/src/internal/utils/base64.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ImageKitError } from '../../core/error'; +import { encodeUTF8 } from './bytes'; + +export const toBase64 = (data: string | Uint8Array | null | undefined): string => { + if (!data) return ''; + + if (typeof (globalThis as any).Buffer !== 'undefined') { + return (globalThis as any).Buffer.from(data).toString('base64'); + } + + if (typeof data === 'string') { + data = encodeUTF8(data); + } + + if (typeof btoa !== 'undefined') { + return btoa(String.fromCharCode.apply(null, data as any)); + } + + throw new ImageKitError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); +}; + +export const fromBase64 = (str: string): Uint8Array => { + if (typeof (globalThis as any).Buffer !== 'undefined') { + const buf = (globalThis as any).Buffer.from(str, 'base64'); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + if (typeof atob !== 'undefined') { + const bstr = atob(str); + const buf = new Uint8Array(bstr.length); + for (let i = 0; i < bstr.length; i++) { + buf[i] = bstr.charCodeAt(i); + } + return buf; + } + + throw new ImageKitError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); +}; diff --git a/src/internal/utils/bytes.ts b/src/internal/utils/bytes.ts new file mode 100644 index 00000000..8da627ab --- /dev/null +++ b/src/internal/utils/bytes.ts @@ -0,0 +1,32 @@ +export function concatBytes(buffers: Uint8Array[]): Uint8Array { + let length = 0; + for (const buffer of buffers) { + length += buffer.length; + } + const output = new Uint8Array(length); + let index = 0; + for (const buffer of buffers) { + output.set(buffer, index); + index += buffer.length; + } + + return output; +} + +let encodeUTF8_: (str: string) => Uint8Array; +export function encodeUTF8(str: string) { + let encoder; + return ( + encodeUTF8_ ?? + ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) + )(str); +} + +let decodeUTF8_: (bytes: Uint8Array) => string; +export function decodeUTF8(bytes: Uint8Array) { + let decoder; + return ( + decodeUTF8_ ?? + ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) + )(bytes); +} diff --git a/src/internal/utils/env.ts b/src/internal/utils/env.ts new file mode 100644 index 00000000..2d848007 --- /dev/null +++ b/src/internal/utils/env.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * Read an environment variable. + * + * Trims beginning and trailing whitespace. + * + * Will return undefined if the environment variable doesn't exist or cannot be accessed. + */ +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim() ?? undefined; + } + if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return undefined; +}; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts new file mode 100644 index 00000000..7da41295 --- /dev/null +++ b/src/internal/utils/log.ts @@ -0,0 +1,126 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { hasOwn } from './values'; +import { type ImageKit } from '../../client'; +import { RequestOptions } from '../request-options'; + +type LogFn = (message: string, ...rest: unknown[]) => void; +export type Logger = { + error: LogFn; + warn: LogFn; + info: LogFn; + debug: LogFn; +}; +export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; + +const levelNumbers = { + off: 0, + error: 200, + warn: 300, + info: 400, + debug: 500, +}; + +export const parseLogLevel = ( + maybeLevel: string | undefined, + sourceName: string, + client: ImageKit, +): LogLevel | undefined => { + if (!maybeLevel) { + return undefined; + } + if (hasOwn(levelNumbers, maybeLevel)) { + return maybeLevel; + } + loggerFor(client).warn( + `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( + Object.keys(levelNumbers), + )}`, + ); + return undefined; +}; + +function noop() {} + +function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { + if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { + return noop; + } else { + // Don't wrap logger functions, we want the stacktrace intact! + return logger[fnLevel].bind(logger); + } +} + +const noopLogger = { + error: noop, + warn: noop, + info: noop, + debug: noop, +}; + +let cachedLoggers = /* @__PURE__ */ new WeakMap(); + +export function loggerFor(client: ImageKit): Logger { + const logger = client.logger; + const logLevel = client.logLevel ?? 'off'; + if (!logger) { + return noopLogger; + } + + const cachedLogger = cachedLoggers.get(logger); + if (cachedLogger && cachedLogger[0] === logLevel) { + return cachedLogger[1]; + } + + const levelLogger = { + error: makeLogFn('error', logger, logLevel), + warn: makeLogFn('warn', logger, logLevel), + info: makeLogFn('info', logger, logLevel), + debug: makeLogFn('debug', logger, logLevel), + }; + + cachedLoggers.set(logger, [logLevel, levelLogger]); + + return levelLogger; +} + +export const formatRequestDetails = (details: { + options?: RequestOptions | undefined; + headers?: Headers | Record | undefined; + retryOfRequestLogID?: string | undefined; + retryOf?: string | undefined; + url?: string | undefined; + status?: number | undefined; + method?: string | undefined; + durationMs?: number | undefined; + message?: unknown; + body?: unknown; +}) => { + if (details.options) { + details.options = { ...details.options }; + delete details.options['headers']; // redundant + leaks internals + } + if (details.headers) { + details.headers = Object.fromEntries( + (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( + ([name, value]) => [ + name, + ( + name.toLowerCase() === 'authorization' || + name.toLowerCase() === 'cookie' || + name.toLowerCase() === 'set-cookie' + ) ? + '***' + : value, + ], + ), + ); + } + if ('retryOfRequestLogID' in details) { + if (details.retryOfRequestLogID) { + details.retryOf = details.retryOfRequestLogID; + } + delete details.retryOfRequestLogID; + } + return details; +}; diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts new file mode 100644 index 00000000..dbf06649 --- /dev/null +++ b/src/internal/utils/path.ts @@ -0,0 +1,88 @@ +import { ImageKitError } from '../../core/error'; + +/** + * Percent-encode everything that isn't safe to have in a path without encoding safe chars. + * + * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: + * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ +export function encodeURIPath(str: string) { + return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); +} + +const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); + +export const createPathTagFunction = (pathEncoder = encodeURIPath) => + function path(statics: readonly string[], ...params: readonly unknown[]): string { + // If there are no params, no processing is needed. + if (statics.length === 1) return statics[0]!; + + let postPath = false; + const invalidSegments = []; + const path = statics.reduce((previousValue, currentValue, index) => { + if (/[?#]/.test(currentValue)) { + postPath = true; + } + const value = params[index]; + let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); + if ( + index !== params.length && + (value == null || + (typeof value === 'object' && + // handle values from other realms + value.toString === + Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) + ?.toString)) + ) { + encoded = value + ''; + invalidSegments.push({ + start: previousValue.length + currentValue.length, + length: encoded.length, + error: `Value of type ${Object.prototype.toString + .call(value) + .slice(8, -1)} is not a valid path parameter`, + }); + } + return previousValue + currentValue + (index === params.length ? '' : encoded); + }, ''); + + const pathOnly = path.split(/[?#]/, 1)[0]!; + const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; + let match; + + // Find all invalid segments + while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { + invalidSegments.push({ + start: match.index, + length: match[0].length, + error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, + }); + } + + invalidSegments.sort((a, b) => a.start - b.start); + + if (invalidSegments.length > 0) { + let lastEnd = 0; + const underline = invalidSegments.reduce((acc, segment) => { + const spaces = ' '.repeat(segment.start - lastEnd); + const arrows = '^'.repeat(segment.length); + lastEnd = segment.start + segment.length; + return acc + spaces + arrows; + }, ''); + + throw new ImageKitError( + `Path parameters result in path with invalid segments:\n${invalidSegments + .map((e) => e.error) + .join('\n')}\n${path}\n${underline}`, + ); + } + + return path; + }; + +/** + * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. + */ +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/internal/utils/sleep.ts b/src/internal/utils/sleep.ts new file mode 100644 index 00000000..65e52962 --- /dev/null +++ b/src/internal/utils/sleep.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/internal/utils/uuid.ts b/src/internal/utils/uuid.ts new file mode 100644 index 00000000..b0e53aaf --- /dev/null +++ b/src/internal/utils/uuid.ts @@ -0,0 +1,17 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +/** + * https://stackoverflow.com/a/2117523 + */ +export let uuid4 = function () { + const { crypto } = globalThis as any; + if (crypto?.randomUUID) { + uuid4 = crypto.randomUUID.bind(crypto); + return crypto.randomUUID(); + } + const u8 = new Uint8Array(1); + const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => + (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), + ); +}; diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts new file mode 100644 index 00000000..532f2c31 --- /dev/null +++ b/src/internal/utils/values.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ImageKitError } from '../../core/error'; + +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; + +export const isAbsoluteURL = (url: string): boolean => { + return startsWithSchemeRegexp.test(url); +}; + +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + +/** Returns an object if the given value isn't an object, otherwise returns as-is */ +export function maybeObj(x: unknown): object { + if (typeof x !== 'object') { + return {}; + } + + return x ?? {}; +} + +// https://stackoverflow.com/a/34491287 +export function isEmptyObj(obj: Object | null | undefined): boolean { + if (!obj) return true; + for (const _k in obj) return false; + return true; +} + +// https://eslint.org/docs/latest/rules/no-prototype-builtins +export function hasOwn(obj: T, key: PropertyKey): key is keyof T { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +export function isObj(obj: unknown): obj is Record { + return obj != null && typeof obj === 'object' && !Array.isArray(obj); +} + +export const ensurePresent = (value: T | null | undefined): T => { + if (value == null) { + throw new ImageKitError(`Expected a value to be given but received ${value} instead.`); + } + + return value; +}; + +export const validatePositiveInteger = (name: string, n: unknown): number => { + if (typeof n !== 'number' || !Number.isInteger(n)) { + throw new ImageKitError(`${name} must be an integer`); + } + if (n < 0) { + throw new ImageKitError(`${name} must be a positive integer`); + } + return n; +}; + +export const coerceInteger = (value: unknown): number => { + if (typeof value === 'number') return Math.round(value); + if (typeof value === 'string') return parseInt(value, 10); + + throw new ImageKitError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceFloat = (value: unknown): number => { + if (typeof value === 'number') return value; + if (typeof value === 'string') return parseFloat(value); + + throw new ImageKitError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') return value === 'true'; + return Boolean(value); +}; + +export const maybeCoerceInteger = (value: unknown): number | undefined => { + if (value === undefined) { + return undefined; + } + return coerceInteger(value); +}; + +export const maybeCoerceFloat = (value: unknown): number | undefined => { + if (value === undefined) { + return undefined; + } + return coerceFloat(value); +}; + +export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { + if (value === undefined) { + return undefined; + } + return coerceBoolean(value); +}; + +export const safeJSON = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return undefined; + } +}; diff --git a/src/lib/.keep b/src/lib/.keep new file mode 100644 index 00000000..7554f8b2 --- /dev/null +++ b/src/lib/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store custom files to expand the SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/src/resource.ts b/src/resource.ts new file mode 100644 index 00000000..363e3516 --- /dev/null +++ b/src/resource.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/resource instead */ +export * from './core/resource'; diff --git a/src/resources.ts b/src/resources.ts new file mode 100644 index 00000000..b283d578 --- /dev/null +++ b/src/resources.ts @@ -0,0 +1 @@ +export * from './resources/index'; diff --git a/src/resources/accounts.ts b/src/resources/accounts.ts new file mode 100644 index 00000000..b9db2991 --- /dev/null +++ b/src/resources/accounts.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './accounts/index'; diff --git a/src/resources/accounts/accounts.ts b/src/resources/accounts/accounts.ts new file mode 100644 index 00000000..82d1b238 --- /dev/null +++ b/src/resources/accounts/accounts.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as OriginsAPI from './origins'; +import { + OriginCreateParams, + OriginListResponse, + OriginRequest, + OriginResponse, + OriginUpdateParams, + Origins, +} from './origins'; +import * as URLEndpointsAPI from './url-endpoints'; +import { + URLEndpointCreateParams, + URLEndpointListResponse, + URLEndpointRequest, + URLEndpointResponse, + URLEndpointUpdateParams, + URLEndpoints, +} from './url-endpoints'; +import * as UsageAPI from './usage'; +import { Usage, UsageGetParams, UsageGetResponse } from './usage'; + +export class Accounts extends APIResource { + usage: UsageAPI.Usage = new UsageAPI.Usage(this._client); + origins: OriginsAPI.Origins = new OriginsAPI.Origins(this._client); + urlEndpoints: URLEndpointsAPI.URLEndpoints = new URLEndpointsAPI.URLEndpoints(this._client); +} + +Accounts.Usage = Usage; +Accounts.Origins = Origins; +Accounts.URLEndpoints = URLEndpoints; + +export declare namespace Accounts { + export { Usage as Usage, type UsageGetResponse as UsageGetResponse, type UsageGetParams as UsageGetParams }; + + export { + Origins as Origins, + type OriginRequest as OriginRequest, + type OriginResponse as OriginResponse, + type OriginListResponse as OriginListResponse, + type OriginCreateParams as OriginCreateParams, + type OriginUpdateParams as OriginUpdateParams, + }; + + export { + URLEndpoints as URLEndpoints, + type URLEndpointRequest as URLEndpointRequest, + type URLEndpointResponse as URLEndpointResponse, + type URLEndpointListResponse as URLEndpointListResponse, + type URLEndpointCreateParams as URLEndpointCreateParams, + type URLEndpointUpdateParams as URLEndpointUpdateParams, + }; +} diff --git a/src/resources/accounts/index.ts b/src/resources/accounts/index.ts new file mode 100644 index 00000000..34a0246d --- /dev/null +++ b/src/resources/accounts/index.ts @@ -0,0 +1,20 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Accounts } from './accounts'; +export { + Origins, + type OriginRequest, + type OriginResponse, + type OriginListResponse, + type OriginCreateParams, + type OriginUpdateParams, +} from './origins'; +export { + URLEndpoints, + type URLEndpointRequest, + type URLEndpointResponse, + type URLEndpointListResponse, + type URLEndpointCreateParams, + type URLEndpointUpdateParams, +} from './url-endpoints'; +export { Usage, type UsageGetResponse, type UsageGetParams } from './usage'; diff --git a/src/resources/accounts/origins.ts b/src/resources/accounts/origins.ts new file mode 100644 index 00000000..39730cfe --- /dev/null +++ b/src/resources/accounts/origins.ts @@ -0,0 +1,700 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Origins extends APIResource { + /** + * **Note:** This API is currently in beta. + * Creates a new origin and returns the origin object. + * + * @example + * ```ts + * const originResponse = await client.accounts.origins.create( + * { + * origin: { + * accessKey: 'AKIATEST123', + * bucket: 'test-bucket', + * name: 'My S3 Origin', + * secretKey: 'secrettest123', + * type: 'S3', + * }, + * }, + * ); + * ``` + */ + create(params: OriginCreateParams, options?: RequestOptions): APIPromise { + const { origin } = params; + return this._client.post('/v1/accounts/origins', { body: origin, ...options }); + } + + /** + * **Note:** This API is currently in beta. + * Updates the origin identified by `id` and returns the updated origin object. + * + * @example + * ```ts + * const originResponse = await client.accounts.origins.update( + * 'id', + * { + * origin: { + * accessKey: 'AKIATEST123', + * bucket: 'test-bucket', + * name: 'My S3 Origin', + * secretKey: 'secrettest123', + * type: 'S3', + * }, + * }, + * ); + * ``` + */ + update(id: string, params: OriginUpdateParams, options?: RequestOptions): APIPromise { + const { origin } = params; + return this._client.put(path`/v1/accounts/origins/${id}`, { body: origin, ...options }); + } + + /** + * **Note:** This API is currently in beta. + * Returns an array of all configured origins for the current account. + * + * @example + * ```ts + * const originResponses = + * await client.accounts.origins.list(); + * ``` + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/v1/accounts/origins', options); + } + + /** + * **Note:** This API is currently in beta. + * Permanently removes the origin identified by `id`. If the origin is in use by + * any URL‑endpoints, the API will return an error. + * + * @example + * ```ts + * await client.accounts.origins.delete('id'); + * ``` + */ + delete(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/v1/accounts/origins/${id}`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * **Note:** This API is currently in beta. + * Retrieves the origin identified by `id`. + * + * @example + * ```ts + * const originResponse = await client.accounts.origins.get( + * 'id', + * ); + * ``` + */ + get(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/accounts/origins/${id}`, options); + } +} + +/** + * Schema for origin request resources. + */ +export type OriginRequest = + | OriginRequest.S3 + | OriginRequest.S3Compatible + | OriginRequest.CloudinaryBackup + | OriginRequest.WebFolder + | OriginRequest.WebProxy + | OriginRequest.Gcs + | OriginRequest.AzureBlob + | OriginRequest.AkeneoPim; + +export namespace OriginRequest { + export interface S3 { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface S3Compatible { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Custom S3-compatible endpoint. + */ + endpoint: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3_COMPATIBLE'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + + /** + * Use path-style S3 URLs? + */ + s3ForcePathStyle?: boolean; + } + + export interface CloudinaryBackup { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'CLOUDINARY_BACKUP'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface WebFolder { + /** + * Root URL for the web folder origin. + */ + baseUrl: string; + + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_FOLDER'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Forward the Host header to origin? + */ + forwardHostHeaderToOrigin?: boolean; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface WebProxy { + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_PROXY'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface Gcs { + bucket: string; + + clientEmail: string; + + /** + * Display name of the origin. + */ + name: string; + + privateKey: string; + + type: 'GCS'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AzureBlob { + accountName: string; + + container: string; + + /** + * Display name of the origin. + */ + name: string; + + sasToken: string; + + type: 'AZURE_BLOB'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AkeneoPim { + /** + * Akeneo instance base URL. + */ + baseUrl: string; + + /** + * Akeneo API client ID. + */ + clientId: string; + + /** + * Akeneo API client secret. + */ + clientSecret: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Akeneo API password. + */ + password: string; + + type: 'AKENEO_PIM'; + + /** + * Akeneo API username. + */ + username: string; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } +} + +/** + * Origin object as returned by the API (sensitive fields removed). + */ +export type OriginResponse = + | OriginResponse.S3 + | OriginResponse.S3Compatible + | OriginResponse.CloudinaryBackup + | OriginResponse.WebFolder + | OriginResponse.WebProxy + | OriginResponse.Gcs + | OriginResponse.AzureBlob + | OriginResponse.AkeneoPim; + +export namespace OriginResponse { + export interface S3 { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Path prefix inside the bucket. + */ + prefix: string; + + type: 'S3'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface S3Compatible { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Custom S3-compatible endpoint. + */ + endpoint: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Path prefix inside the bucket. + */ + prefix: string; + + /** + * Use path-style S3 URLs? + */ + s3ForcePathStyle: boolean; + + type: 'S3_COMPATIBLE'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface CloudinaryBackup { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Path prefix inside the bucket. + */ + prefix: string; + + type: 'CLOUDINARY_BACKUP'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface WebFolder { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * Root URL for the web folder origin. + */ + baseUrl: string; + + /** + * Forward the Host header to origin? + */ + forwardHostHeaderToOrigin: boolean; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_FOLDER'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface WebProxy { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_PROXY'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface Gcs { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + bucket: string; + + clientEmail: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + prefix: string; + + type: 'GCS'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface AzureBlob { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + accountName: string; + + container: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + prefix: string; + + type: 'AZURE_BLOB'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } + + export interface AkeneoPim { + /** + * Unique identifier for the origin. This is generated by ImageKit when you create + * a new origin. + */ + id: string; + + /** + * Akeneo instance base URL. + */ + baseUrl: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader: boolean; + + /** + * Display name of the origin. + */ + name: string; + + type: 'AKENEO_PIM'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + } +} + +export type OriginListResponse = Array; + +export interface OriginCreateParams { + /** + * Schema for origin request resources. + */ + origin: OriginRequest; +} + +export interface OriginUpdateParams { + /** + * Schema for origin request resources. + */ + origin: OriginRequest; +} + +export declare namespace Origins { + export { + type OriginRequest as OriginRequest, + type OriginResponse as OriginResponse, + type OriginListResponse as OriginListResponse, + type OriginCreateParams as OriginCreateParams, + type OriginUpdateParams as OriginUpdateParams, + }; +} diff --git a/src/resources/accounts/url-endpoints.ts b/src/resources/accounts/url-endpoints.ts new file mode 100644 index 00000000..7bd424c4 --- /dev/null +++ b/src/resources/accounts/url-endpoints.ts @@ -0,0 +1,298 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class URLEndpoints extends APIResource { + /** + * **Note:** This API is currently in beta. + * Creates a new URL‑endpoint and returns the resulting object. + * + * @example + * ```ts + * const urlEndpointResponse = + * await client.accounts.urlEndpoints.create({ + * description: 'My custom URL endpoint', + * }); + * ``` + */ + create(body: URLEndpointCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/accounts/url-endpoints', { body, ...options }); + } + + /** + * **Note:** This API is currently in beta. + * Updates the URL‑endpoint identified by `id` and returns the updated object. + * + * @example + * ```ts + * const urlEndpointResponse = + * await client.accounts.urlEndpoints.update('id', { + * description: 'My custom URL endpoint', + * }); + * ``` + */ + update( + id: string, + body: URLEndpointUpdateParams, + options?: RequestOptions, + ): APIPromise { + return this._client.put(path`/v1/accounts/url-endpoints/${id}`, { body, ...options }); + } + + /** + * **Note:** This API is currently in beta. + * Returns an array of all URL‑endpoints configured including the default + * URL-endpoint generated by ImageKit during account creation. + * + * @example + * ```ts + * const urlEndpointResponses = + * await client.accounts.urlEndpoints.list(); + * ``` + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/v1/accounts/url-endpoints', options); + } + + /** + * **Note:** This API is currently in beta. + * Deletes the URL‑endpoint identified by `id`. You cannot delete the default + * URL‑endpoint created by ImageKit during account creation. + * + * @example + * ```ts + * await client.accounts.urlEndpoints.delete('id'); + * ``` + */ + delete(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/v1/accounts/url-endpoints/${id}`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * **Note:** This API is currently in beta. + * Retrieves the URL‑endpoint identified by `id`. + * + * @example + * ```ts + * const urlEndpointResponse = + * await client.accounts.urlEndpoints.get('id'); + * ``` + */ + get(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/accounts/url-endpoints/${id}`, options); + } +} + +/** + * Schema for URL endpoint resource. + */ +export interface URLEndpointRequest { + /** + * Description of the URL endpoint. + */ + description: string; + + /** + * Ordered list of origin IDs to try when the file isn’t in the Media Library; + * ImageKit checks them in the sequence provided. Origin must be created before it + * can be used in a URL endpoint. + */ + origins?: Array; + + /** + * Path segment appended to your base URL to form the endpoint (letters, digits, + * and hyphens only — or empty for the default endpoint). + */ + urlPrefix?: string; + + /** + * Configuration for third-party URL rewriting. + */ + urlRewriter?: URLEndpointRequest.Cloudinary | URLEndpointRequest.Imgix | URLEndpointRequest.Akamai; +} + +export namespace URLEndpointRequest { + export interface Cloudinary { + type: 'CLOUDINARY'; + + /** + * Whether to preserve `/` in the rewritten URL. + */ + preserveAssetDeliveryTypes?: boolean; + } + + export interface Imgix { + type: 'IMGIX'; + } + + export interface Akamai { + type: 'AKAMAI'; + } +} + +/** + * URL‑endpoint object as returned by the API. + */ +export interface URLEndpointResponse { + /** + * Unique identifier for the URL-endpoint. This is generated by ImageKit when you + * create a new URL-endpoint. For the default URL-endpoint, this is always + * `default`. + */ + id: string; + + /** + * Description of the URL endpoint. + */ + description: string; + + /** + * Ordered list of origin IDs to try when the file isn’t in the Media Library; + * ImageKit checks them in the sequence provided. Origin must be created before it + * can be used in a URL endpoint. + */ + origins: Array; + + /** + * Path segment appended to your base URL to form the endpoint (letters, digits, + * and hyphens only — or empty for the default endpoint). + */ + urlPrefix: string; + + /** + * Configuration for third-party URL rewriting. + */ + urlRewriter?: URLEndpointResponse.Cloudinary | URLEndpointResponse.Imgix | URLEndpointResponse.Akamai; +} + +export namespace URLEndpointResponse { + export interface Cloudinary { + /** + * Whether to preserve `/` in the rewritten URL. + */ + preserveAssetDeliveryTypes: boolean; + + type: 'CLOUDINARY'; + } + + export interface Imgix { + type: 'IMGIX'; + } + + export interface Akamai { + type: 'AKAMAI'; + } +} + +export type URLEndpointListResponse = Array; + +export interface URLEndpointCreateParams { + /** + * Description of the URL endpoint. + */ + description: string; + + /** + * Ordered list of origin IDs to try when the file isn’t in the Media Library; + * ImageKit checks them in the sequence provided. Origin must be created before it + * can be used in a URL endpoint. + */ + origins?: Array; + + /** + * Path segment appended to your base URL to form the endpoint (letters, digits, + * and hyphens only — or empty for the default endpoint). + */ + urlPrefix?: string; + + /** + * Configuration for third-party URL rewriting. + */ + urlRewriter?: + | URLEndpointCreateParams.Cloudinary + | URLEndpointCreateParams.Imgix + | URLEndpointCreateParams.Akamai; +} + +export namespace URLEndpointCreateParams { + export interface Cloudinary { + type: 'CLOUDINARY'; + + /** + * Whether to preserve `/` in the rewritten URL. + */ + preserveAssetDeliveryTypes?: boolean; + } + + export interface Imgix { + type: 'IMGIX'; + } + + export interface Akamai { + type: 'AKAMAI'; + } +} + +export interface URLEndpointUpdateParams { + /** + * Description of the URL endpoint. + */ + description: string; + + /** + * Ordered list of origin IDs to try when the file isn’t in the Media Library; + * ImageKit checks them in the sequence provided. Origin must be created before it + * can be used in a URL endpoint. + */ + origins?: Array; + + /** + * Path segment appended to your base URL to form the endpoint (letters, digits, + * and hyphens only — or empty for the default endpoint). + */ + urlPrefix?: string; + + /** + * Configuration for third-party URL rewriting. + */ + urlRewriter?: + | URLEndpointUpdateParams.Cloudinary + | URLEndpointUpdateParams.Imgix + | URLEndpointUpdateParams.Akamai; +} + +export namespace URLEndpointUpdateParams { + export interface Cloudinary { + type: 'CLOUDINARY'; + + /** + * Whether to preserve `/` in the rewritten URL. + */ + preserveAssetDeliveryTypes?: boolean; + } + + export interface Imgix { + type: 'IMGIX'; + } + + export interface Akamai { + type: 'AKAMAI'; + } +} + +export declare namespace URLEndpoints { + export { + type URLEndpointRequest as URLEndpointRequest, + type URLEndpointResponse as URLEndpointResponse, + type URLEndpointListResponse as URLEndpointListResponse, + type URLEndpointCreateParams as URLEndpointCreateParams, + type URLEndpointUpdateParams as URLEndpointUpdateParams, + }; +} diff --git a/src/resources/accounts/usage.ts b/src/resources/accounts/usage.ts new file mode 100644 index 00000000..87e703a4 --- /dev/null +++ b/src/resources/accounts/usage.ts @@ -0,0 +1,70 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; + +export class Usage extends APIResource { + /** + * Get the account usage information between two dates. Note that the API response + * includes data from the start date while excluding data from the end date. In + * other words, the data covers the period starting from the specified start date + * up to, but not including, the end date. + * + * @example + * ```ts + * const usage = await client.accounts.usage.get({ + * endDate: '2019-12-27', + * startDate: '2019-12-27', + * }); + * ``` + */ + get(query: UsageGetParams, options?: RequestOptions): APIPromise { + return this._client.get('/v1/accounts/usage', { query, ...options }); + } +} + +export interface UsageGetResponse { + /** + * Amount of bandwidth used in bytes. + */ + bandwidthBytes?: number; + + /** + * Number of extension units used. + */ + extensionUnitsCount?: number; + + /** + * Storage used by media library in bytes. + */ + mediaLibraryStorageBytes?: number; + + /** + * Storage used by the original cache in bytes. + */ + originalCacheStorageBytes?: number; + + /** + * Number of video processing units used. + */ + videoProcessingUnitsCount?: number; +} + +export interface UsageGetParams { + /** + * Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. + * The difference between `startDate` and `endDate` should be less than 90 days. + */ + endDate: string; + + /** + * Specify a `startDate` in `YYYY-MM-DD` format. It should be before the `endDate`. + * The difference between `startDate` and `endDate` should be less than 90 days. + */ + startDate: string; +} + +export declare namespace Usage { + export { type UsageGetResponse as UsageGetResponse, type UsageGetParams as UsageGetParams }; +} diff --git a/src/resources/assets.ts b/src/resources/assets.ts new file mode 100644 index 00000000..19a35adb --- /dev/null +++ b/src/resources/assets.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as FilesAPI from './files/files'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; + +export class Assets extends APIResource { + /** + * This API can list all the uploaded files and folders in your ImageKit.io media + * library. In addition, you can fine-tune your query by specifying various filters + * by generating a query string in a Lucene-like syntax and provide this generated + * string as the value of the `searchQuery`. + */ + list( + query: AssetListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/v1/files', { query, ...options }); + } +} + +export type AssetListResponse = Array; + +export interface AssetListParams { + /** + * Filter results by file type. + * + * - `all` — include all file types + * - `image` — include only image files + * - `non-image` — include only non-image files (e.g., JS, CSS, video) + */ + fileType?: 'all' | 'image' | 'non-image'; + + /** + * The maximum number of results to return in response. + */ + limit?: number; + + /** + * Folder path if you want to limit the search within a specific folder. For + * example, `/sales-banner/` will only search in folder sales-banner. + * + * Note : If your use case involves searching within a folder as well as its + * subfolders, you can use `path` parameter in `searchQuery` with appropriate + * operator. Checkout + * [Supported parameters](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#supported-parameters) + * for more information. + */ + path?: string; + + /** + * Query string in a Lucene-like query language e.g. `createdAt > "7d"`. + * + * Note : When the searchQuery parameter is present, the following query parameters + * will have no effect on the result: + * + * 1. `tags` + * 2. `type` + * 3. `name` + * + * [Learn more](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#advanced-search-queries) + * from examples. + */ + searchQuery?: string; + + /** + * The number of results to skip before returning results. + */ + skip?: number; + + /** + * Sort the results by one of the supported fields in ascending or descending + * order. + */ + sort?: + | 'ASC_NAME' + | 'DESC_NAME' + | 'ASC_CREATED' + | 'DESC_CREATED' + | 'ASC_UPDATED' + | 'DESC_UPDATED' + | 'ASC_HEIGHT' + | 'DESC_HEIGHT' + | 'ASC_WIDTH' + | 'DESC_WIDTH' + | 'ASC_SIZE' + | 'DESC_SIZE' + | 'ASC_RELEVANCE' + | 'DESC_RELEVANCE'; + + /** + * Filter results by asset type. + * + * - `file` — returns only files + * - `file-version` — returns specific file versions + * - `folder` — returns only folders + * - `all` — returns both files and folders (excludes `file-version`) + */ + type?: 'file' | 'file-version' | 'folder' | 'all'; +} + +export declare namespace Assets { + export { type AssetListResponse as AssetListResponse, type AssetListParams as AssetListParams }; +} diff --git a/src/resources/beta.ts b/src/resources/beta.ts new file mode 100644 index 00000000..1542e942 --- /dev/null +++ b/src/resources/beta.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './beta/index'; diff --git a/src/resources/beta/beta.ts b/src/resources/beta/beta.ts new file mode 100644 index 00000000..f8fa04c2 --- /dev/null +++ b/src/resources/beta/beta.ts @@ -0,0 +1,15 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as V2API from './v2/v2'; +import { V2 } from './v2/v2'; + +export class Beta extends APIResource { + v2: V2API.V2 = new V2API.V2(this._client); +} + +Beta.V2 = V2; + +export declare namespace Beta { + export { V2 as V2 }; +} diff --git a/src/resources/beta/index.ts b/src/resources/beta/index.ts new file mode 100644 index 00000000..2b3a43ce --- /dev/null +++ b/src/resources/beta/index.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Beta } from './beta'; +export { V2 } from './v2/index'; diff --git a/src/resources/beta/v2.ts b/src/resources/beta/v2.ts new file mode 100644 index 00000000..ca56a446 --- /dev/null +++ b/src/resources/beta/v2.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './v2/index'; diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts new file mode 100644 index 00000000..17bc06b2 --- /dev/null +++ b/src/resources/beta/v2/files.ts @@ -0,0 +1,604 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../../core/resource'; +import * as FilesAPI from '../../files/files'; +import { APIPromise } from '../../../core/api-promise'; +import { type Uploadable } from '../../../core/uploads'; +import { RequestOptions } from '../../../internal/request-options'; +import { multipartFormRequestOptions } from '../../../internal/uploads'; + +export class Files extends APIResource { + /** + * The V2 API enhances security by verifying the entire payload using JWT. This API + * is in beta. + * + * ImageKit.io allows you to upload files directly from both the server and client + * sides. For server-side uploads, private API key authentication is used. For + * client-side uploads, generate a one-time `token` from your secure backend using + * private API. + * [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) + * about how to implement secure client-side file upload. + * + * **File size limit** \ + * On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw + * files, and 100MB for videos. On the paid plan, these limits increase to 40MB for + * images, audio, and raw files, and 2GB for videos. These limits can be further increased + * with higher-tier plans. + * + * **Version limit** \ + * A file can have a maximum of 100 versions. + * + * **Demo applications** + * + * - A full-fledged + * [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), + * supporting file selections from local storage, URL, Dropbox, Google Drive, + * Instagram, and more. + * - [Quick start guides](/docs/quick-start-guides) for various frameworks and + * technologies. + * + * @example + * ```ts + * const response = await client.beta.v2.files.upload({ + * file: fs.createReadStream('path/to/file'), + * fileName: 'fileName', + * }); + * ``` + */ + upload(body: FileUploadParams, options?: RequestOptions): APIPromise { + return this._client.post( + '/api/v2/files/upload', + multipartFormRequestOptions( + { body, defaultBaseURL: 'https://upload.imagekit.io', ...options }, + this._client, + ), + ); + } +} + +/** + * Object containing details of a successful upload. + */ +export interface FileUploadResponse { + /** + * An array of tags assigned to the uploaded file by auto tagging. + */ + AITags?: Array | null; + + /** + * The audio codec used in the video (only for video). + */ + audioCodec?: string; + + /** + * The bit rate of the video in kbps (only for video). + */ + bitRate?: number; + + /** + * Value of custom coordinates associated with the image in the format + * `x,y,width,height`. If `customCoordinates` are not defined, then it is `null`. + * Send `customCoordinates` in `responseFields` in API request to get the value of + * this field. + */ + customCoordinates?: string | null; + + /** + * A key-value data associated with the asset. Use `responseField` in API request + * to get `customMetadata` in the upload API response. Before setting any custom + * metadata on an asset, you have to create the field using custom metadata fields + * API. Send `customMetadata` in `responseFields` in API request to get the value + * of this field. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. Can be set by the user or + * the ai-auto-description extension. + */ + description?: string; + + /** + * The duration of the video in seconds (only for video). + */ + duration?: number; + + /** + * Consolidated embedded metadata associated with the file. It includes exif, iptc, + * and xmp data. Send `embeddedMetadata` in `responseFields` in API request to get + * embeddedMetadata in the upload API response. + */ + embeddedMetadata?: { [key: string]: unknown }; + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + extensionStatus?: FileUploadResponse.ExtensionStatus; + + /** + * Unique fileId. Store this fileld in your database, as this will be used to + * perform update action on this file. + */ + fileId?: string; + + /** + * The relative path of the file in the media library e.g. + * `/marketing-assets/new-banner.jpg`. + */ + filePath?: string; + + /** + * Type of the uploaded file. Possible values are `image`, `non-image`. + */ + fileType?: string; + + /** + * Height of the image in pixels (Only for images) + */ + height?: number; + + /** + * Is the file marked as private. It can be either `true` or `false`. Send + * `isPrivateFile` in `responseFields` in API request to get the value of this + * field. + */ + isPrivateFile?: boolean; + + /** + * Is the file published or in draft state. It can be either `true` or `false`. + * Send `isPublished` in `responseFields` in API request to get the value of this + * field. + */ + isPublished?: boolean; + + /** + * Legacy metadata. Send `metadata` in `responseFields` in API request to get + * metadata in the upload API response. + */ + metadata?: FilesAPI.Metadata; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Size of the image file in Bytes. + */ + size?: number; + + /** + * The array of tags associated with the asset. If no tags are set, it will be + * `null`. Send `tags` in `responseFields` in API request to get the value of this + * field. + */ + tags?: Array | null; + + /** + * In the case of an image, a small thumbnail URL. + */ + thumbnailUrl?: string; + + /** + * A publicly accessible URL of the file. + */ + url?: string; + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + versionInfo?: FileUploadResponse.VersionInfo; + + /** + * The video codec used in the video (only for video). + */ + videoCodec?: string; + + /** + * Width of the image in pixels (Only for Images) + */ + width?: number; +} + +export namespace FileUploadResponse { + export interface AITag { + /** + * Confidence score of the tag. + */ + confidence?: number; + + /** + * Name of the tag. + */ + name?: string; + + /** + * Array of `AITags` associated with the image. If no `AITags` are set, it will be + * null. These tags can be added using the `google-auto-tagging` or + * `aws-auto-tagging` extensions. + */ + source?: string; + } + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + export interface ExtensionStatus { + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'remove-bg'?: 'success' | 'pending' | 'failed'; + } + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + export interface VersionInfo { + /** + * Unique identifier of the file version. + */ + id?: string; + + /** + * Name of the file version. + */ + name?: string; + } +} + +export interface FileUploadParams { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; + + /** + * The name with which the file has to be uploaded. + */ + fileName: string; + + /** + * This is the client-generated JSON Web Token (JWT). The ImageKit.io server uses + * it to authenticate and check that the upload request parameters have not been + * tampered with after the token has been generated. Learn how to create the token + * on the page below. This field is only required for authentication when uploading + * a file from the client side. + * + * **Note**: Sending a JWT that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new token. + * + * **⚠️Warning**: JWT must be generated on the server-side because it is generated + * using your account's private API key. This field is required for authentication + * when uploading a file from the client-side. + */ + token?: string; + + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file-v2#upload-api-checks). + */ + checks?: string; + + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; + + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription + >; + + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. Using multiple `/` creates a nested + * folder. + */ + folder?: string; + + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; + + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; + + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; + + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; + + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; + + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; + + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; + + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadParams.Transformation; + + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; +} + +export namespace FileUploadParams { + export interface RemoveBg { + /** + * Specifies the background removal extension. + */ + name: 'remove-bg'; + + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { + /** + * Maximum number of tags to attach to the asset. + */ + maxTags: number; + + /** + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { + /** + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. + */ + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; + + /** + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. + */ + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } + } +} + +export declare namespace Files { + export { type FileUploadResponse as FileUploadResponse, type FileUploadParams as FileUploadParams }; +} diff --git a/src/resources/beta/v2/index.ts b/src/resources/beta/v2/index.ts new file mode 100644 index 00000000..bffa32ec --- /dev/null +++ b/src/resources/beta/v2/index.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Files, type FileUploadResponse, type FileUploadParams } from './files'; +export { V2 } from './v2'; diff --git a/src/resources/beta/v2/v2.ts b/src/resources/beta/v2/v2.ts new file mode 100644 index 00000000..3a047920 --- /dev/null +++ b/src/resources/beta/v2/v2.ts @@ -0,0 +1,19 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../../core/resource'; +import * as FilesAPI from './files'; +import { FileUploadParams, FileUploadResponse, Files } from './files'; + +export class V2 extends APIResource { + files: FilesAPI.Files = new FilesAPI.Files(this._client); +} + +V2.Files = Files; + +export declare namespace V2 { + export { + Files as Files, + type FileUploadResponse as FileUploadResponse, + type FileUploadParams as FileUploadParams, + }; +} diff --git a/src/resources/cache.ts b/src/resources/cache.ts new file mode 100644 index 00000000..c011d115 --- /dev/null +++ b/src/resources/cache.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './cache/index'; diff --git a/src/resources/cache/cache.ts b/src/resources/cache/cache.ts new file mode 100644 index 00000000..b959081c --- /dev/null +++ b/src/resources/cache/cache.ts @@ -0,0 +1,25 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as InvalidationAPI from './invalidation'; +import { + Invalidation, + InvalidationCreateParams, + InvalidationCreateResponse, + InvalidationGetResponse, +} from './invalidation'; + +export class Cache extends APIResource { + invalidation: InvalidationAPI.Invalidation = new InvalidationAPI.Invalidation(this._client); +} + +Cache.Invalidation = Invalidation; + +export declare namespace Cache { + export { + Invalidation as Invalidation, + type InvalidationCreateResponse as InvalidationCreateResponse, + type InvalidationGetResponse as InvalidationGetResponse, + type InvalidationCreateParams as InvalidationCreateParams, + }; +} diff --git a/src/resources/cache/index.ts b/src/resources/cache/index.ts new file mode 100644 index 00000000..3c9f4fc7 --- /dev/null +++ b/src/resources/cache/index.ts @@ -0,0 +1,9 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Cache } from './cache'; +export { + Invalidation, + type InvalidationCreateResponse, + type InvalidationGetResponse, + type InvalidationCreateParams, +} from './invalidation'; diff --git a/src/resources/cache/invalidation.ts b/src/resources/cache/invalidation.ts new file mode 100644 index 00000000..d06b2701 --- /dev/null +++ b/src/resources/cache/invalidation.ts @@ -0,0 +1,70 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Invalidation extends APIResource { + /** + * This API will purge CDN cache and ImageKit.io's internal cache for a file. Note: + * Purge cache is an asynchronous process and it may take some time to reflect the + * changes. + * + * @example + * ```ts + * const invalidation = await client.cache.invalidation.create( + * { + * url: 'https://ik.imagekit.io/your_imagekit_id/default-image.jpg', + * }, + * ); + * ``` + */ + create(body: InvalidationCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/purge', { body, ...options }); + } + + /** + * This API returns the status of a purge cache request. + * + * @example + * ```ts + * const invalidation = await client.cache.invalidation.get( + * 'requestId', + * ); + * ``` + */ + get(requestID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/files/purge/${requestID}`, options); + } +} + +export interface InvalidationCreateResponse { + /** + * Unique identifier of the purge request. This can be used to check the status of + * the purge request. + */ + requestId?: string; +} + +export interface InvalidationGetResponse { + /** + * Status of the purge request. + */ + status?: 'Pending' | 'Completed'; +} + +export interface InvalidationCreateParams { + /** + * The full URL of the file to be purged. + */ + url: string; +} + +export declare namespace Invalidation { + export { + type InvalidationCreateResponse as InvalidationCreateResponse, + type InvalidationGetResponse as InvalidationGetResponse, + type InvalidationCreateParams as InvalidationCreateParams, + }; +} diff --git a/src/resources/custom-metadata-fields.ts b/src/resources/custom-metadata-fields.ts new file mode 100644 index 00000000..10a917e4 --- /dev/null +++ b/src/resources/custom-metadata-fields.ts @@ -0,0 +1,337 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; +import { path } from '../internal/utils/path'; + +export class CustomMetadataFields extends APIResource { + /** + * This API creates a new custom metadata field. Once a custom metadata field is + * created either through this API or using the dashboard UI, its value can be set + * on the assets. The value of a field for an asset can be set using the media + * library UI or programmatically through upload or update assets API. + * + * @example + * ```ts + * const customMetadataField = + * await client.customMetadataFields.create({ + * label: 'price', + * name: 'price', + * schema: { + * type: 'Number', + * minValue: 1000, + * maxValue: 3000, + * }, + * }); + * ``` + */ + create(body: CustomMetadataFieldCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/customMetadataFields', { body, ...options }); + } + + /** + * This API updates the label or schema of an existing custom metadata field. + * + * @example + * ```ts + * const customMetadataField = + * await client.customMetadataFields.update('id', { + * label: 'price', + * schema: { + * type: 'Number', + * minValue: 1000, + * maxValue: 3000, + * }, + * }); + * ``` + */ + update( + id: string, + body: CustomMetadataFieldUpdateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.patch(path`/v1/customMetadataFields/${id}`, { body, ...options }); + } + + /** + * This API returns the array of created custom metadata field objects. By default + * the API returns only non deleted field objects, but you can include deleted + * fields in the API response. + * + * @example + * ```ts + * const customMetadataFields = + * await client.customMetadataFields.list(); + * ``` + */ + list( + query: CustomMetadataFieldListParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.get('/v1/customMetadataFields', { query, ...options }); + } + + /** + * This API deletes a custom metadata field. Even after deleting a custom metadata + * field, you cannot create any new custom metadata field with the same name. + * + * @example + * ```ts + * const customMetadataField = + * await client.customMetadataFields.delete('id'); + * ``` + */ + delete(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/v1/customMetadataFields/${id}`, options); + } +} + +/** + * Object containing details of a custom metadata field. + */ +export interface CustomMetadataField { + /** + * Unique identifier for the custom metadata field. Use this to update the field. + */ + id: string; + + /** + * Human readable name of the custom metadata field. This name is displayed as form + * field label to the users while setting field value on the asset in the media + * library UI. + */ + label: string; + + /** + * API name of the custom metadata field. This becomes the key while setting + * `customMetadata` (key-value object) for an asset using upload or update API. + */ + name: string; + + /** + * An object that describes the rules for the custom metadata field value. + */ + schema: CustomMetadataField.Schema; +} + +export namespace CustomMetadataField { + /** + * An object that describes the rules for the custom metadata field value. + */ + export interface Schema { + /** + * Type of the custom metadata field. + */ + type: 'Text' | 'Textarea' | 'Number' | 'Date' | 'Boolean' | 'SingleSelect' | 'MultiSelect'; + + /** + * The default value for this custom metadata field. Date type of default value + * depends on the field type. + */ + defaultValue?: string | number | boolean | Array; + + /** + * Specifies if the this custom metadata field is required or not. + */ + isValueRequired?: boolean; + + /** + * Maximum length of string. Only set if `type` is set to `Text` or `Textarea`. + */ + maxLength?: number; + + /** + * Maximum value of the field. Only set if field type is `Date` or `Number`. For + * `Date` type field, the value will be in ISO8601 string format. For `Number` type + * field, it will be a numeric value. + */ + maxValue?: string | number; + + /** + * Minimum length of string. Only set if `type` is set to `Text` or `Textarea`. + */ + minLength?: number; + + /** + * Minimum value of the field. Only set if field type is `Date` or `Number`. For + * `Date` type field, the value will be in ISO8601 string format. For `Number` type + * field, it will be a numeric value. + */ + minValue?: string | number; + + /** + * An array of allowed values when field type is `SingleSelect` or `MultiSelect`. + */ + selectOptions?: Array; + } +} + +export type CustomMetadataFieldListResponse = Array; + +export interface CustomMetadataFieldDeleteResponse {} + +export interface CustomMetadataFieldCreateParams { + /** + * Human readable name of the custom metadata field. This should be unique across + * all non deleted custom metadata fields. This name is displayed as form field + * label to the users while setting field value on an asset in the media library + * UI. + */ + label: string; + + /** + * API name of the custom metadata field. This should be unique across all + * (including deleted) custom metadata fields. + */ + name: string; + + schema: CustomMetadataFieldCreateParams.Schema; +} + +export namespace CustomMetadataFieldCreateParams { + export interface Schema { + /** + * Type of the custom metadata field. + */ + type: 'Text' | 'Textarea' | 'Number' | 'Date' | 'Boolean' | 'SingleSelect' | 'MultiSelect'; + + /** + * The default value for this custom metadata field. This property is only required + * if `isValueRequired` property is set to `true`. The value should match the + * `type` of custom metadata field. + */ + defaultValue?: string | number | boolean | Array; + + /** + * Sets this custom metadata field as required. Setting custom metadata fields on + * an asset will throw error if the value for all required fields are not present + * in upload or update asset API request body. + */ + isValueRequired?: boolean; + + /** + * Maximum length of string. Only set this property if `type` is set to `Text` or + * `Textarea`. + */ + maxLength?: number; + + /** + * Maximum value of the field. Only set this property if field type is `Date` or + * `Number`. For `Date` type field, set the minimum date in ISO8601 string format. + * For `Number` type field, set the minimum numeric value. + */ + maxValue?: string | number; + + /** + * Minimum length of string. Only set this property if `type` is set to `Text` or + * `Textarea`. + */ + minLength?: number; + + /** + * Minimum value of the field. Only set this property if field type is `Date` or + * `Number`. For `Date` type field, set the minimum date in ISO8601 string format. + * For `Number` type field, set the minimum numeric value. + */ + minValue?: string | number; + + /** + * An array of allowed values. This property is only required if `type` property is + * set to `SingleSelect` or `MultiSelect`. + */ + selectOptions?: Array; + } +} + +export interface CustomMetadataFieldUpdateParams { + /** + * Human readable name of the custom metadata field. This should be unique across + * all non deleted custom metadata fields. This name is displayed as form field + * label to the users while setting field value on an asset in the media library + * UI. This parameter is required if `schema` is not provided. + */ + label?: string; + + /** + * An object that describes the rules for the custom metadata key. This parameter + * is required if `label` is not provided. Note: `type` cannot be updated and will + * be ignored if sent with the `schema`. The schema will be validated as per the + * existing `type`. + */ + schema?: CustomMetadataFieldUpdateParams.Schema; +} + +export namespace CustomMetadataFieldUpdateParams { + /** + * An object that describes the rules for the custom metadata key. This parameter + * is required if `label` is not provided. Note: `type` cannot be updated and will + * be ignored if sent with the `schema`. The schema will be validated as per the + * existing `type`. + */ + export interface Schema { + /** + * The default value for this custom metadata field. This property is only required + * if `isValueRequired` property is set to `true`. The value should match the + * `type` of custom metadata field. + */ + defaultValue?: string | number | boolean | Array; + + /** + * Sets this custom metadata field as required. Setting custom metadata fields on + * an asset will throw error if the value for all required fields are not present + * in upload or update asset API request body. + */ + isValueRequired?: boolean; + + /** + * Maximum length of string. Only set this property if `type` is set to `Text` or + * `Textarea`. + */ + maxLength?: number; + + /** + * Maximum value of the field. Only set this property if field type is `Date` or + * `Number`. For `Date` type field, set the minimum date in ISO8601 string format. + * For `Number` type field, set the minimum numeric value. + */ + maxValue?: string | number; + + /** + * Minimum length of string. Only set this property if `type` is set to `Text` or + * `Textarea`. + */ + minLength?: number; + + /** + * Minimum value of the field. Only set this property if field type is `Date` or + * `Number`. For `Date` type field, set the minimum date in ISO8601 string format. + * For `Number` type field, set the minimum numeric value. + */ + minValue?: string | number; + + /** + * An array of allowed values. This property is only required if `type` property is + * set to `SingleSelect` or `MultiSelect`. + */ + selectOptions?: Array; + } +} + +export interface CustomMetadataFieldListParams { + /** + * Set it to `true` to include deleted field objects in the API response. + */ + includeDeleted?: boolean; +} + +export declare namespace CustomMetadataFields { + export { + type CustomMetadataField as CustomMetadataField, + type CustomMetadataFieldListResponse as CustomMetadataFieldListResponse, + type CustomMetadataFieldDeleteResponse as CustomMetadataFieldDeleteResponse, + type CustomMetadataFieldCreateParams as CustomMetadataFieldCreateParams, + type CustomMetadataFieldUpdateParams as CustomMetadataFieldUpdateParams, + type CustomMetadataFieldListParams as CustomMetadataFieldListParams, + }; +} diff --git a/src/resources/files.ts b/src/resources/files.ts new file mode 100644 index 00000000..46a5299c --- /dev/null +++ b/src/resources/files.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './files/index'; diff --git a/src/resources/files/bulk.ts b/src/resources/files/bulk.ts new file mode 100644 index 00000000..35dec26f --- /dev/null +++ b/src/resources/files/bulk.ts @@ -0,0 +1,171 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; + +export class Bulk extends APIResource { + /** + * This API deletes multiple files and all their file versions permanently. + * + * Note: If a file or specific transformation has been requested in the past, then + * the response is cached. Deleting a file does not purge the cache. You can purge + * the cache using purge cache API. + * + * A maximum of 100 files can be deleted at a time. + * + * @example + * ```ts + * const bulk = await client.files.bulk.delete({ + * fileIds: [ + * '598821f949c0a938d57563bd', + * '598821f949c0a938d57563be', + * ], + * }); + * ``` + */ + delete(body: BulkDeleteParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/batch/deleteByFileIds', { body, ...options }); + } + + /** + * This API adds tags to multiple files in bulk. A maximum of 50 files can be + * specified at a time. + * + * @example + * ```ts + * const response = await client.files.bulk.addTags({ + * fileIds: [ + * '598821f949c0a938d57563bd', + * '598821f949c0a938d57563be', + * ], + * tags: ['t-shirt', 'round-neck', 'sale2019'], + * }); + * ``` + */ + addTags(body: BulkAddTagsParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/addTags', { body, ...options }); + } + + /** + * This API removes AITags from multiple files in bulk. A maximum of 50 files can + * be specified at a time. + * + * @example + * ```ts + * const response = await client.files.bulk.removeAITags({ + * AITags: ['t-shirt', 'round-neck', 'sale2019'], + * fileIds: [ + * '598821f949c0a938d57563bd', + * '598821f949c0a938d57563be', + * ], + * }); + * ``` + */ + removeAITags(body: BulkRemoveAITagsParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/removeAITags', { body, ...options }); + } + + /** + * This API removes tags from multiple files in bulk. A maximum of 50 files can be + * specified at a time. + * + * @example + * ```ts + * const response = await client.files.bulk.removeTags({ + * fileIds: [ + * '598821f949c0a938d57563bd', + * '598821f949c0a938d57563be', + * ], + * tags: ['t-shirt', 'round-neck', 'sale2019'], + * }); + * ``` + */ + removeTags(body: BulkRemoveTagsParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/removeTags', { body, ...options }); + } +} + +export interface BulkDeleteResponse { + /** + * An array of fileIds that were successfully deleted. + */ + successfullyDeletedFileIds?: Array; +} + +export interface BulkAddTagsResponse { + /** + * An array of fileIds that in which tags were successfully added. + */ + successfullyUpdatedFileIds?: Array; +} + +export interface BulkRemoveAITagsResponse { + /** + * An array of fileIds that in which AITags were successfully removed. + */ + successfullyUpdatedFileIds?: Array; +} + +export interface BulkRemoveTagsResponse { + /** + * An array of fileIds that in which tags were successfully removed. + */ + successfullyUpdatedFileIds?: Array; +} + +export interface BulkDeleteParams { + /** + * An array of fileIds which you want to delete. + */ + fileIds: Array; +} + +export interface BulkAddTagsParams { + /** + * An array of fileIds to which you want to add tags. + */ + fileIds: Array; + + /** + * An array of tags that you want to add to the files. + */ + tags: Array; +} + +export interface BulkRemoveAITagsParams { + /** + * An array of AITags that you want to remove from the files. + */ + AITags: Array; + + /** + * An array of fileIds from which you want to remove AITags. + */ + fileIds: Array; +} + +export interface BulkRemoveTagsParams { + /** + * An array of fileIds from which you want to remove tags. + */ + fileIds: Array; + + /** + * An array of tags that you want to remove from the files. + */ + tags: Array; +} + +export declare namespace Bulk { + export { + type BulkDeleteResponse as BulkDeleteResponse, + type BulkAddTagsResponse as BulkAddTagsResponse, + type BulkRemoveAITagsResponse as BulkRemoveAITagsResponse, + type BulkRemoveTagsResponse as BulkRemoveTagsResponse, + type BulkDeleteParams as BulkDeleteParams, + type BulkAddTagsParams as BulkAddTagsParams, + type BulkRemoveAITagsParams as BulkRemoveAITagsParams, + type BulkRemoveTagsParams as BulkRemoveTagsParams, + }; +} diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts new file mode 100644 index 00000000..4e6b5a54 --- /dev/null +++ b/src/resources/files/files.ts @@ -0,0 +1,1476 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as BulkAPI from './bulk'; +import { + Bulk, + BulkAddTagsParams, + BulkAddTagsResponse, + BulkDeleteParams, + BulkDeleteResponse, + BulkRemoveAITagsParams, + BulkRemoveAITagsResponse, + BulkRemoveTagsParams, + BulkRemoveTagsResponse, +} from './bulk'; +import * as MetadataAPI from './metadata'; +import { MetadataGetFromURLParams } from './metadata'; +import * as VersionsAPI from './versions'; +import { + VersionDeleteParams, + VersionDeleteResponse, + VersionGetParams, + VersionListResponse, + VersionRestoreParams, + Versions, +} from './versions'; +import { APIPromise } from '../../core/api-promise'; +import { type Uploadable } from '../../core/uploads'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { multipartFormRequestOptions } from '../../internal/uploads'; +import { path } from '../../internal/utils/path'; + +export class Files extends APIResource { + bulk: BulkAPI.Bulk = new BulkAPI.Bulk(this._client); + versions: VersionsAPI.Versions = new VersionsAPI.Versions(this._client); + metadata: MetadataAPI.Metadata = new MetadataAPI.Metadata(this._client); + + /** + * This API updates the details or attributes of the current version of the file. + * You can update `tags`, `customCoordinates`, `customMetadata`, publication + * status, remove existing `AITags` and apply extensions using this API. + * + * @example + * ```ts + * const file = await client.files.update('fileId'); + * ``` + */ + update( + fileID: string, + params: FileUpdateParams | null | undefined = undefined, + options?: RequestOptions, + ): APIPromise { + const { update } = params ?? {}; + return this._client.patch(path`/v1/files/${fileID}/details`, { body: update, ...options }); + } + + /** + * This API deletes the file and all its file versions permanently. + * + * Note: If a file or specific transformation has been requested in the past, then + * the response is cached. Deleting a file does not purge the cache. You can purge + * the cache using purge cache API. + * + * @example + * ```ts + * await client.files.delete('fileId'); + * ``` + */ + delete(fileID: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/v1/files/${fileID}`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * This will copy a file from one folder to another. + * + * Note: If any file at the destination has the same name as the source file, then + * the source file and its versions (if `includeFileVersions` is set to true) will + * be appended to the destination file version history. + * + * @example + * ```ts + * const response = await client.files.copy({ + * destinationPath: '/folder/to/copy/into/', + * sourceFilePath: '/path/to/file.jpg', + * }); + * ``` + */ + copy(body: FileCopyParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/copy', { body, ...options }); + } + + /** + * This API returns an object with details or attributes about the current version + * of the file. + * + * @example + * ```ts + * const file = await client.files.get('fileId'); + * ``` + */ + get(fileID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/files/${fileID}/details`, options); + } + + /** + * This will move a file and all its versions from one folder to another. + * + * Note: If any file at the destination has the same name as the source file, then + * the source file and its versions will be appended to the destination file. + * + * @example + * ```ts + * const response = await client.files.move({ + * destinationPath: '/folder/to/move/into/', + * sourceFilePath: '/path/to/file.jpg', + * }); + * ``` + */ + move(body: FileMoveParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/files/move', { body, ...options }); + } + + /** + * You can rename an already existing file in the media library using rename file + * API. This operation would rename all file versions of the file. + * + * Note: The old URLs will stop working. The file/file version URLs cached on CDN + * will continue to work unless a purge is requested. + * + * @example + * ```ts + * const response = await client.files.rename({ + * filePath: '/path/to/file.jpg', + * newFileName: 'newFileName.jpg', + * }); + * ``` + */ + rename(body: FileRenameParams, options?: RequestOptions): APIPromise { + return this._client.put('/v1/files/rename', { body, ...options }); + } + + /** + * ImageKit.io allows you to upload files directly from both the server and client + * sides. For server-side uploads, private API key authentication is used. For + * client-side uploads, generate a one-time `token`, `signature`, and `expire` from + * your secure backend using private API. + * [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) + * about how to implement client-side file upload. + * + * The [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security + * by verifying the entire payload using JWT. + * + * **File size limit** \ + * On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw + * files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, + * audio, and raw files and 2GB for videos. These limits can be further increased with + * higher-tier plans. + * + * **Version limit** \ + * A file can have a maximum of 100 versions. + * + * **Demo applications** + * + * - A full-fledged + * [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), + * supporting file selections from local storage, URL, Dropbox, Google Drive, + * Instagram, and more. + * - [Quick start guides](/docs/quick-start-guides) for various frameworks and + * technologies. + * + * @example + * ```ts + * const response = await client.files.upload({ + * file: fs.createReadStream('path/to/file'), + * fileName: 'fileName', + * }); + * ``` + */ + upload(body: FileUploadParams, options?: RequestOptions): APIPromise { + return this._client.post( + '/api/v1/files/upload', + multipartFormRequestOptions( + { body, defaultBaseURL: 'https://upload.imagekit.io', ...options }, + this._client, + ), + ); + } +} + +/** + * Object containing details of a file or file version. + */ +export interface File { + /** + * An array of tags assigned to the file by auto tagging. + */ + AITags?: Array | null; + + /** + * Date and time when the file was uploaded. The date and time is in ISO8601 + * format. + */ + createdAt?: string; + + /** + * An string with custom coordinates of the file. + */ + customCoordinates?: string | null; + + /** + * An object with custom metadata for the file. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. Can be set by the user or + * the ai-auto-description extension. + */ + description?: string; + + /** + * Unique identifier of the asset. + */ + fileId?: string; + + /** + * Path of the file. This is the path you would use in the URL to access the file. + * For example, if the file is at the root of the media library, the path will be + * `/file.jpg`. If the file is inside a folder named `images`, the path will be + * `/images/file.jpg`. + */ + filePath?: string; + + /** + * Type of the file. Possible values are `image`, `non-image`. + */ + fileType?: string; + + /** + * Specifies if the image has an alpha channel. + */ + hasAlpha?: boolean; + + /** + * Height of the file. + */ + height?: number; + + /** + * Specifies if the file is private or not. + */ + isPrivateFile?: boolean; + + /** + * Specifies if the file is published or not. + */ + isPublished?: boolean; + + /** + * MIME type of the file. + */ + mime?: string; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Size of the file in bytes. + */ + size?: number; + + /** + * An array of tags assigned to the file. Tags are used to search files in the + * media library. + */ + tags?: Array | null; + + /** + * URL of the thumbnail image. This URL is used to access the thumbnail image of + * the file in the media library. + */ + thumbnail?: string; + + /** + * Type of the asset. + */ + type?: 'file' | 'file-version'; + + /** + * Date and time when the file was last updated. The date and time is in ISO8601 + * format. + */ + updatedAt?: string; + + /** + * URL of the file. + */ + url?: string; + + /** + * An object with details of the file version. + */ + versionInfo?: File.VersionInfo; + + /** + * Width of the file. + */ + width?: number; +} + +export namespace File { + export interface AITag { + /** + * Confidence score of the tag. + */ + confidence?: number; + + /** + * Name of the tag. + */ + name?: string; + + /** + * Source of the tag. Possible values are `google-auto-tagging` and + * `aws-auto-tagging`. + */ + source?: string; + } + + /** + * An object with details of the file version. + */ + export interface VersionInfo { + /** + * Unique identifier of the file version. + */ + id?: string; + + /** + * Name of the file version. + */ + name?: string; + } +} + +export interface Folder { + /** + * Date and time when the folder was created. The date and time is in ISO8601 + * format. + */ + createdAt?: string; + + /** + * Unique identifier of the asset. + */ + folderId?: string; + + /** + * Path of the folder. This is the path you would use in the URL to access the + * folder. For example, if the folder is at the root of the media library, the path + * will be /folder. If the folder is inside another folder named images, the path + * will be /images/folder. + */ + folderPath?: string; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Type of the asset. + */ + type?: 'folder'; + + /** + * Date and time when the folder was last updated. The date and time is in ISO8601 + * format. + */ + updatedAt?: string; +} + +/** + * JSON object containing metadata. + */ +export interface Metadata { + /** + * The audio codec used in the video (only for video). + */ + audioCodec?: string; + + /** + * The bit rate of the video in kbps (only for video). + */ + bitRate?: number; + + /** + * The density of the image in DPI. + */ + density?: number; + + /** + * The duration of the video in seconds (only for video). + */ + duration?: number; + + exif?: Metadata.Exif; + + /** + * The format of the file (e.g., 'jpg', 'mp4'). + */ + format?: string; + + /** + * Indicates if the image has a color profile. + */ + hasColorProfile?: boolean; + + /** + * Indicates if the image contains transparent areas. + */ + hasTransparency?: boolean; + + /** + * The height of the image or video in pixels. + */ + height?: number; + + /** + * Perceptual hash of the image. + */ + pHash?: string; + + /** + * The quality indicator of the image. + */ + quality?: number; + + /** + * The file size in bytes. + */ + size?: number; + + /** + * The video codec used in the video (only for video). + */ + videoCodec?: string; + + /** + * The width of the image or video in pixels. + */ + width?: number; +} + +export namespace Metadata { + export interface Exif { + /** + * Object containing Exif details. + */ + exif?: Exif.Exif; + + /** + * Object containing GPS information. + */ + gps?: Exif.Gps; + + /** + * Object containing EXIF image information. + */ + image?: Exif.Image; + + /** + * JSON object. + */ + interoperability?: Exif.Interoperability; + + makernote?: { [key: string]: unknown }; + + /** + * Object containing Thumbnail information. + */ + thumbnail?: Exif.Thumbnail; + } + + export namespace Exif { + /** + * Object containing Exif details. + */ + export interface Exif { + ApertureValue?: number; + + ColorSpace?: number; + + CreateDate?: string; + + CustomRendered?: number; + + DateTimeOriginal?: string; + + ExifImageHeight?: number; + + ExifImageWidth?: number; + + ExifVersion?: string; + + ExposureCompensation?: number; + + ExposureMode?: number; + + ExposureProgram?: number; + + ExposureTime?: number; + + Flash?: number; + + FlashpixVersion?: string; + + FNumber?: number; + + FocalLength?: number; + + FocalPlaneResolutionUnit?: number; + + FocalPlaneXResolution?: number; + + FocalPlaneYResolution?: number; + + InteropOffset?: number; + + ISO?: number; + + MeteringMode?: number; + + SceneCaptureType?: number; + + ShutterSpeedValue?: number; + + SubSecTime?: string; + + WhiteBalance?: number; + } + + /** + * Object containing GPS information. + */ + export interface Gps { + GPSVersionID?: Array; + } + + /** + * Object containing EXIF image information. + */ + export interface Image { + ExifOffset?: number; + + GPSInfo?: number; + + Make?: string; + + Model?: string; + + ModifyDate?: string; + + Orientation?: number; + + ResolutionUnit?: number; + + Software?: string; + + XResolution?: number; + + YCbCrPositioning?: number; + + YResolution?: number; + } + + /** + * JSON object. + */ + export interface Interoperability { + InteropIndex?: string; + + InteropVersion?: string; + } + + /** + * Object containing Thumbnail information. + */ + export interface Thumbnail { + Compression?: number; + + ResolutionUnit?: number; + + ThumbnailLength?: number; + + ThumbnailOffset?: number; + + XResolution?: number; + + YResolution?: number; + } + } +} + +/** + * Object containing details of a file or file version. + */ +export interface FileUpdateResponse extends File { + extensionStatus?: FileUpdateResponse.ExtensionStatus; +} + +export namespace FileUpdateResponse { + export interface ExtensionStatus { + 'ai-auto-description'?: 'success' | 'pending' | 'failed'; + + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'remove-bg'?: 'success' | 'pending' | 'failed'; + } +} + +export interface FileCopyResponse {} + +export interface FileMoveResponse {} + +export interface FileRenameResponse { + /** + * Unique identifier of the purge request. This can be used to check the status of + * the purge request. + */ + purgeRequestId?: string; +} + +/** + * Object containing details of a successful upload. + */ +export interface FileUploadResponse { + /** + * An array of tags assigned to the uploaded file by auto tagging. + */ + AITags?: Array | null; + + /** + * The audio codec used in the video (only for video). + */ + audioCodec?: string; + + /** + * The bit rate of the video in kbps (only for video). + */ + bitRate?: number; + + /** + * Value of custom coordinates associated with the image in the format + * `x,y,width,height`. If `customCoordinates` are not defined, then it is `null`. + * Send `customCoordinates` in `responseFields` in API request to get the value of + * this field. + */ + customCoordinates?: string | null; + + /** + * A key-value data associated with the asset. Use `responseField` in API request + * to get `customMetadata` in the upload API response. Before setting any custom + * metadata on an asset, you have to create the field using custom metadata fields + * API. Send `customMetadata` in `responseFields` in API request to get the value + * of this field. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. Can be set by the user or + * the ai-auto-description extension. + */ + description?: string; + + /** + * The duration of the video in seconds (only for video). + */ + duration?: number; + + /** + * Consolidated embedded metadata associated with the file. It includes exif, iptc, + * and xmp data. Send `embeddedMetadata` in `responseFields` in API request to get + * embeddedMetadata in the upload API response. + */ + embeddedMetadata?: { [key: string]: unknown }; + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + extensionStatus?: FileUploadResponse.ExtensionStatus; + + /** + * Unique fileId. Store this fileld in your database, as this will be used to + * perform update action on this file. + */ + fileId?: string; + + /** + * The relative path of the file in the media library e.g. + * `/marketing-assets/new-banner.jpg`. + */ + filePath?: string; + + /** + * Type of the uploaded file. Possible values are `image`, `non-image`. + */ + fileType?: string; + + /** + * Height of the image in pixels (Only for images) + */ + height?: number; + + /** + * Is the file marked as private. It can be either `true` or `false`. Send + * `isPrivateFile` in `responseFields` in API request to get the value of this + * field. + */ + isPrivateFile?: boolean; + + /** + * Is the file published or in draft state. It can be either `true` or `false`. + * Send `isPublished` in `responseFields` in API request to get the value of this + * field. + */ + isPublished?: boolean; + + /** + * Legacy metadata. Send `metadata` in `responseFields` in API request to get + * metadata in the upload API response. + */ + metadata?: Metadata; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Size of the image file in Bytes. + */ + size?: number; + + /** + * The array of tags associated with the asset. If no tags are set, it will be + * `null`. Send `tags` in `responseFields` in API request to get the value of this + * field. + */ + tags?: Array | null; + + /** + * In the case of an image, a small thumbnail URL. + */ + thumbnailUrl?: string; + + /** + * A publicly accessible URL of the file. + */ + url?: string; + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + versionInfo?: FileUploadResponse.VersionInfo; + + /** + * The video codec used in the video (only for video). + */ + videoCodec?: string; + + /** + * Width of the image in pixels (Only for Images) + */ + width?: number; +} + +export namespace FileUploadResponse { + export interface AITag { + /** + * Confidence score of the tag. + */ + confidence?: number; + + /** + * Name of the tag. + */ + name?: string; + + /** + * Array of `AITags` associated with the image. If no `AITags` are set, it will be + * null. These tags can be added using the `google-auto-tagging` or + * `aws-auto-tagging` extensions. + */ + source?: string; + } + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + export interface ExtensionStatus { + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'remove-bg'?: 'success' | 'pending' | 'failed'; + } + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + export interface VersionInfo { + /** + * Unique identifier of the file version. + */ + id?: string; + + /** + * Name of the file version. + */ + name?: string; + } +} + +export interface FileUpdateParams { + update?: FileUpdateParams.UpdateFileDetails | FileUpdateParams.ChangePublicationStatus; +} + +export namespace FileUpdateParams { + export interface UpdateFileDetails { + /** + * Define an important area in the image in the format `x,y,width,height` e.g. + * `10,10,100,100`. Send `null` to unset this value. + */ + customCoordinates?: string | null; + + /** + * A key-value data to be associated with the asset. To unset a key, send `null` + * value for that key. Before setting any custom metadata on an asset you have to + * create the field using custom metadata fields API. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * Array of extensions to be applied to the asset. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + | UpdateFileDetails.RemoveBg + | UpdateFileDetails.AutoTaggingExtension + | UpdateFileDetails.AIAutoDescription + >; + + /** + * An array of AITags associated with the file that you want to remove, e.g. + * `["car", "vehicle", "motorsports"]`. + * + * If you want to remove all AITags associated with the file, send a string - + * "all". + * + * Note: The remove operation for `AITags` executes before any of the `extensions` + * are processed. + */ + removeAITags?: Array | 'all'; + + /** + * An array of tags associated with the file, such as `["tag1", "tag2"]`. Send + * `null` to unset all tags associated with the file. + */ + tags?: Array | null; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; + } + + export namespace UpdateFileDetails { + export interface RemoveBg { + /** + * Specifies the background removal extension. + */ + name: 'remove-bg'; + + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { + /** + * Maximum number of tags to attach to the asset. + */ + maxTags: number; + + /** + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } + } + + export interface ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + publish?: ChangePublicationStatus.Publish; + } + + export namespace ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + export interface Publish { + /** + * Set to `true` to publish the file. Set to `false` to unpublish the file. + */ + isPublished: boolean; + + /** + * Set to `true` to publish/unpublish all versions of the file. Set to `false` to + * publish/unpublish only the current version of the file. + */ + includeFileVersions?: boolean; + } + } +} + +export interface FileCopyParams { + /** + * Full path to the folder you want to copy the above file into. + */ + destinationPath: string; + + /** + * The full path of the file you want to copy. + */ + sourceFilePath: string; + + /** + * Option to copy all versions of a file. By default, only the current version of + * the file is copied. When set to true, all versions of the file will be copied. + * Default value - `false`. + */ + includeFileVersions?: boolean; +} + +export interface FileMoveParams { + /** + * Full path to the folder you want to move the above file into. + */ + destinationPath: string; + + /** + * The full path of the file you want to move. + */ + sourceFilePath: string; +} + +export interface FileRenameParams { + /** + * The full path of the file you want to rename. + */ + filePath: string; + + /** + * The new name of the file. A filename can contain: + * + * Alphanumeric Characters: `a-z`, `A-Z`, `0-9` (including Unicode letters, marks, + * and numerals in other languages). Special Characters: `.`, `_`, and `-`. + * + * Any other character, including space, will be replaced by `_`. + */ + newFileName: string; + + /** + * Option to purge cache for the old file and its versions' URLs. + * + * When set to true, it will internally issue a purge cache request on CDN to + * remove cached content of old file and its versions. This purge request is + * counted against your monthly purge quota. + * + * Note: If the old file were accessible at + * `https://ik.imagekit.io/demo/old-filename.jpg`, a purge cache request would be + * issued against `https://ik.imagekit.io/demo/old-filename.jpg*` (with a wildcard + * at the end). It will remove the file and its versions' URLs and any + * transformations made using query parameters on this file or its versions. + * However, the cache for file transformations made using path parameters will + * persist. You can purge them using the purge API. For more details, refer to the + * purge API documentation. + * + * Default value - `false` + */ + purgeCache?: boolean; +} + +export interface FileUploadParams { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; + + /** + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` + */ + fileName: string; + + /** + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. + */ + token?: string; + + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + */ + checks?: string; + + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; + + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; + + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription + >; + + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; + + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; + + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; + + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; + + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; + + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; + + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; + + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; + + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; + + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; + + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadParams.Transformation; + + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; +} + +export namespace FileUploadParams { + export interface RemoveBg { + /** + * Specifies the background removal extension. + */ + name: 'remove-bg'; + + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { + /** + * Maximum number of tags to attach to the asset. + */ + maxTags: number; + + /** + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { + /** + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. + */ + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; + + /** + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. + */ + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } + } +} + +Files.Bulk = Bulk; +Files.Versions = Versions; + +export declare namespace Files { + export { + type File as File, + type Folder as Folder, + type Metadata as Metadata, + type FileUpdateResponse as FileUpdateResponse, + type FileCopyResponse as FileCopyResponse, + type FileMoveResponse as FileMoveResponse, + type FileRenameResponse as FileRenameResponse, + type FileUploadResponse as FileUploadResponse, + type FileUpdateParams as FileUpdateParams, + type FileCopyParams as FileCopyParams, + type FileMoveParams as FileMoveParams, + type FileRenameParams as FileRenameParams, + type FileUploadParams as FileUploadParams, + }; + + export { + Bulk as Bulk, + type BulkDeleteResponse as BulkDeleteResponse, + type BulkAddTagsResponse as BulkAddTagsResponse, + type BulkRemoveAITagsResponse as BulkRemoveAITagsResponse, + type BulkRemoveTagsResponse as BulkRemoveTagsResponse, + type BulkDeleteParams as BulkDeleteParams, + type BulkAddTagsParams as BulkAddTagsParams, + type BulkRemoveAITagsParams as BulkRemoveAITagsParams, + type BulkRemoveTagsParams as BulkRemoveTagsParams, + }; + + export { + Versions as Versions, + type VersionListResponse as VersionListResponse, + type VersionDeleteResponse as VersionDeleteResponse, + type VersionDeleteParams as VersionDeleteParams, + type VersionGetParams as VersionGetParams, + type VersionRestoreParams as VersionRestoreParams, + }; + + export { type MetadataGetFromURLParams as MetadataGetFromURLParams }; +} diff --git a/src/resources/files/index.ts b/src/resources/files/index.ts new file mode 100644 index 00000000..e194b456 --- /dev/null +++ b/src/resources/files/index.ts @@ -0,0 +1,38 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { + Bulk, + type BulkDeleteResponse, + type BulkAddTagsResponse, + type BulkRemoveAITagsResponse, + type BulkRemoveTagsResponse, + type BulkDeleteParams, + type BulkAddTagsParams, + type BulkRemoveAITagsParams, + type BulkRemoveTagsParams, +} from './bulk'; +export { + Files, + type File, + type Folder, + type Metadata, + type FileUpdateResponse, + type FileCopyResponse, + type FileMoveResponse, + type FileRenameResponse, + type FileUploadResponse, + type FileUpdateParams, + type FileCopyParams, + type FileMoveParams, + type FileRenameParams, + type FileUploadParams, +} from './files'; +export { + Versions, + type VersionListResponse, + type VersionDeleteResponse, + type VersionDeleteParams, + type VersionGetParams, + type VersionRestoreParams, +} from './versions'; +export { type MetadataGetFromURLParams } from './metadata'; diff --git a/src/resources/files/metadata.ts b/src/resources/files/metadata.ts new file mode 100644 index 00000000..18416407 --- /dev/null +++ b/src/resources/files/metadata.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as FilesAPI from './files'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Metadata extends APIResource { + /** + * You can programmatically get image EXIF, pHash, and other metadata for uploaded + * files in the ImageKit.io media library using this API. + * + * You can also get the metadata in upload API response by passing `metadata` in + * `responseFields` parameter. + * + * @example + * ```ts + * const metadata = await client.files.metadata.get('fileId'); + * ``` + */ + get(fileID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/files/${fileID}/metadata`, options); + } + + /** + * Get image EXIF, pHash, and other metadata from ImageKit.io powered remote URL + * using this API. + * + * @example + * ```ts + * const metadata = await client.files.metadata.getFromURL({ + * url: 'https://example.com', + * }); + * ``` + */ + getFromURL(query: MetadataGetFromURLParams, options?: RequestOptions): APIPromise { + return this._client.get('/v1/files/metadata', { query, ...options }); + } +} + +export interface MetadataGetFromURLParams { + /** + * Should be a valid file URL. It should be accessible using your ImageKit.io + * account. + */ + url: string; +} + +export declare namespace Metadata { + export { type MetadataGetFromURLParams as MetadataGetFromURLParams }; +} diff --git a/src/resources/files/versions.ts b/src/resources/files/versions.ts new file mode 100644 index 00000000..4cd160bd --- /dev/null +++ b/src/resources/files/versions.ts @@ -0,0 +1,117 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as FilesAPI from './files'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Versions extends APIResource { + /** + * This API returns details of all versions of a file. + * + * @example + * ```ts + * const files = await client.files.versions.list('fileId'); + * ``` + */ + list(fileID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/files/${fileID}/versions`, options); + } + + /** + * This API deletes a non-current file version permanently. The API returns an + * empty response. + * + * Note: If you want to delete all versions of a file, use the delete file API. + * + * @example + * ```ts + * const version = await client.files.versions.delete( + * 'versionId', + * { fileId: 'fileId' }, + * ); + * ``` + */ + delete( + versionID: string, + params: VersionDeleteParams, + options?: RequestOptions, + ): APIPromise { + const { fileId } = params; + return this._client.delete(path`/v1/files/${fileId}/versions/${versionID}`, options); + } + + /** + * This API returns an object with details or attributes of a file version. + * + * @example + * ```ts + * const file = await client.files.versions.get('versionId', { + * fileId: 'fileId', + * }); + * ``` + */ + get(versionID: string, params: VersionGetParams, options?: RequestOptions): APIPromise { + const { fileId } = params; + return this._client.get(path`/v1/files/${fileId}/versions/${versionID}`, options); + } + + /** + * This API restores a file version as the current file version. + * + * @example + * ```ts + * const file = await client.files.versions.restore( + * 'versionId', + * { fileId: 'fileId' }, + * ); + * ``` + */ + restore( + versionID: string, + params: VersionRestoreParams, + options?: RequestOptions, + ): APIPromise { + const { fileId } = params; + return this._client.put(path`/v1/files/${fileId}/versions/${versionID}/restore`, options); + } +} + +export type VersionListResponse = Array; + +export interface VersionDeleteResponse {} + +export interface VersionDeleteParams { + /** + * The unique `fileId` of the uploaded file. `fileId` is returned in list and + * search assets API and upload API. + */ + fileId: string; +} + +export interface VersionGetParams { + /** + * The unique `fileId` of the uploaded file. `fileId` is returned in list and + * search assets API and upload API. + */ + fileId: string; +} + +export interface VersionRestoreParams { + /** + * The unique `fileId` of the uploaded file. `fileId` is returned in list and + * search assets API and upload API. + */ + fileId: string; +} + +export declare namespace Versions { + export { + type VersionListResponse as VersionListResponse, + type VersionDeleteResponse as VersionDeleteResponse, + type VersionDeleteParams as VersionDeleteParams, + type VersionGetParams as VersionGetParams, + type VersionRestoreParams as VersionRestoreParams, + }; +} diff --git a/src/resources/folders.ts b/src/resources/folders.ts new file mode 100644 index 00000000..7aaeee34 --- /dev/null +++ b/src/resources/folders.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './folders/index'; diff --git a/src/resources/folders/folders.ts b/src/resources/folders/folders.ts new file mode 100644 index 00000000..916e3042 --- /dev/null +++ b/src/resources/folders/folders.ts @@ -0,0 +1,249 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as JobAPI from './job'; +import { Job, JobGetResponse } from './job'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; + +export class Folders extends APIResource { + job: JobAPI.Job = new JobAPI.Job(this._client); + + /** + * This will create a new folder. You can specify the folder name and location of + * the parent folder where this new folder should be created. + * + * @example + * ```ts + * const folder = await client.folders.create({ + * folderName: 'summer', + * parentFolderPath: '/product/images/', + * }); + * ``` + */ + create(body: FolderCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/folder', { body, ...options }); + } + + /** + * This will delete a folder and all its contents permanently. The API returns an + * empty response. + * + * @example + * ```ts + * const folder = await client.folders.delete({ + * folderPath: '/folder/to/delete/', + * }); + * ``` + */ + delete(body: FolderDeleteParams, options?: RequestOptions): APIPromise { + return this._client.delete('/v1/folder', { body, ...options }); + } + + /** + * This will copy one folder into another. The selected folder, its nested folders, + * files, and their versions (in `includeVersions` is set to true) are copied in + * this operation. Note: If any file at the destination has the same name as the + * source file, then the source file and its versions will be appended to the + * destination file version history. + * + * @example + * ```ts + * const response = await client.folders.copy({ + * destinationPath: '/path/of/destination/folder', + * sourceFolderPath: '/path/of/source/folder', + * }); + * ``` + */ + copy(body: FolderCopyParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/bulkJobs/copyFolder', { body, ...options }); + } + + /** + * This will move one folder into another. The selected folder, its nested folders, + * files, and their versions are moved in this operation. Note: If any file at the + * destination has the same name as the source file, then the source file and its + * versions will be appended to the destination file version history. + * + * @example + * ```ts + * const response = await client.folders.move({ + * destinationPath: '/path/of/destination/folder', + * sourceFolderPath: '/path/of/source/folder', + * }); + * ``` + */ + move(body: FolderMoveParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/bulkJobs/moveFolder', { body, ...options }); + } + + /** + * This API allows you to rename an existing folder. The folder and all its nested + * assets and sub-folders will remain unchanged, but their paths will be updated to + * reflect the new folder name. + * + * @example + * ```ts + * const response = await client.folders.rename({ + * folderPath: '/path/of/folder', + * newFolderName: 'new-folder-name', + * }); + * ``` + */ + rename(body: FolderRenameParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/bulkJobs/renameFolder', { body, ...options }); + } +} + +export interface FolderCreateResponse {} + +export interface FolderDeleteResponse {} + +/** + * Job submitted successfully. A `jobId` will be returned. + */ +export interface FolderCopyResponse { + /** + * Unique identifier of the bulk job. This can be used to check the status of the + * bulk job. + */ + jobId: string; +} + +/** + * Job submitted successfully. A `jobId` will be returned. + */ +export interface FolderMoveResponse { + /** + * Unique identifier of the bulk job. This can be used to check the status of the + * bulk job. + */ + jobId: string; +} + +/** + * Job submitted successfully. A `jobId` will be returned. + */ +export interface FolderRenameResponse { + /** + * Unique identifier of the bulk job. This can be used to check the status of the + * bulk job. + */ + jobId: string; +} + +export interface FolderCreateParams { + /** + * The folder will be created with this name. + * + * All characters except alphabets and numbers (inclusive of unicode letters, + * marks, and numerals in other languages) will be replaced by an underscore i.e. + * `_`. + */ + folderName: string; + + /** + * The folder where the new folder should be created, for root use `/` else the + * path e.g. `containing/folder/`. + * + * Note: If any folder(s) is not present in the parentFolderPath parameter, it will + * be automatically created. For example, if you pass `/product/images/summer`, + * then `product`, `images`, and `summer` folders will be created if they don't + * already exist. + */ + parentFolderPath: string; +} + +export interface FolderDeleteParams { + /** + * Full path to the folder you want to delete. For example `/folder/to/delete/`. + */ + folderPath: string; +} + +export interface FolderCopyParams { + /** + * Full path to the destination folder where you want to copy the source folder + * into. + */ + destinationPath: string; + + /** + * The full path to the source folder you want to copy. + */ + sourceFolderPath: string; + + /** + * Option to copy all versions of files that are nested inside the selected folder. + * By default, only the current version of each file will be copied. When set to + * true, all versions of each file will be copied. Default value - `false`. + */ + includeVersions?: boolean; +} + +export interface FolderMoveParams { + /** + * Full path to the destination folder where you want to move the source folder + * into. + */ + destinationPath: string; + + /** + * The full path to the source folder you want to move. + */ + sourceFolderPath: string; +} + +export interface FolderRenameParams { + /** + * The full path to the folder you want to rename. + */ + folderPath: string; + + /** + * The new name for the folder. + * + * All characters except alphabets and numbers (inclusive of unicode letters, + * marks, and numerals in other languages) and `-` will be replaced by an + * underscore i.e. `_`. + */ + newFolderName: string; + + /** + * Option to purge cache for the old nested files and their versions' URLs. + * + * When set to true, it will internally issue a purge cache request on CDN to + * remove the cached content of the old nested files and their versions. There will + * only be one purge request for all the nested files, which will be counted + * against your monthly purge quota. + * + * Note: A purge cache request will be issued against + * `https://ik.imagekit.io/old/folder/path*` (with a wildcard at the end). This + * will remove all nested files, their versions' URLs, and any transformations made + * using query parameters on these files or their versions. However, the cache for + * file transformations made using path parameters will persist. You can purge them + * using the purge API. For more details, refer to the purge API documentation. + * + * Default value - `false` + */ + purgeCache?: boolean; +} + +Folders.Job = Job; + +export declare namespace Folders { + export { + type FolderCreateResponse as FolderCreateResponse, + type FolderDeleteResponse as FolderDeleteResponse, + type FolderCopyResponse as FolderCopyResponse, + type FolderMoveResponse as FolderMoveResponse, + type FolderRenameResponse as FolderRenameResponse, + type FolderCreateParams as FolderCreateParams, + type FolderDeleteParams as FolderDeleteParams, + type FolderCopyParams as FolderCopyParams, + type FolderMoveParams as FolderMoveParams, + type FolderRenameParams as FolderRenameParams, + }; + + export { Job as Job, type JobGetResponse as JobGetResponse }; +} diff --git a/src/resources/folders/index.ts b/src/resources/folders/index.ts new file mode 100644 index 00000000..7759a7d4 --- /dev/null +++ b/src/resources/folders/index.ts @@ -0,0 +1,16 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { + Folders, + type FolderCreateResponse, + type FolderDeleteResponse, + type FolderCopyResponse, + type FolderMoveResponse, + type FolderRenameResponse, + type FolderCreateParams, + type FolderDeleteParams, + type FolderCopyParams, + type FolderMoveParams, + type FolderRenameParams, +} from './folders'; +export { Job, type JobGetResponse } from './job'; diff --git a/src/resources/folders/job.ts b/src/resources/folders/job.ts new file mode 100644 index 00000000..a9fc92b6 --- /dev/null +++ b/src/resources/folders/job.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Job extends APIResource { + /** + * This API returns the status of a bulk job like copy and move folder operations. + * + * @example + * ```ts + * const job = await client.folders.job.get('jobId'); + * ``` + */ + get(jobID: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/v1/bulkJobs/${jobID}`, options); + } +} + +export interface JobGetResponse { + /** + * Unique identifier of the bulk job. + */ + jobId?: string; + + /** + * Unique identifier of the purge request. This will be present only if + * `purgeCache` is set to `true` in the rename folder API request. + */ + purgeRequestId?: string; + + /** + * Status of the bulk job. + */ + status?: 'Pending' | 'Completed'; + + /** + * Type of the bulk job. + */ + type?: 'COPY_FOLDER' | 'MOVE_FOLDER' | 'RENAME_FOLDER'; +} + +export declare namespace Job { + export { type JobGetResponse as JobGetResponse }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts new file mode 100644 index 00000000..eb7f8ff3 --- /dev/null +++ b/src/resources/index.ts @@ -0,0 +1,53 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export * from './shared'; +export { Accounts } from './accounts/accounts'; +export { Assets, type AssetListResponse, type AssetListParams } from './assets'; +export { Beta } from './beta/beta'; +export { Cache } from './cache/cache'; +export { + CustomMetadataFields, + type CustomMetadataField, + type CustomMetadataFieldListResponse, + type CustomMetadataFieldDeleteResponse, + type CustomMetadataFieldCreateParams, + type CustomMetadataFieldUpdateParams, + type CustomMetadataFieldListParams, +} from './custom-metadata-fields'; +export { + Files, + type File, + type Folder, + type Metadata, + type FileUpdateResponse, + type FileCopyResponse, + type FileMoveResponse, + type FileRenameResponse, + type FileUploadResponse, + type FileUpdateParams, + type FileCopyParams, + type FileMoveParams, + type FileRenameParams, + type FileUploadParams, +} from './files/files'; +export { + Folders, + type FolderCreateResponse, + type FolderDeleteResponse, + type FolderCopyResponse, + type FolderMoveResponse, + type FolderRenameResponse, + type FolderCreateParams, + type FolderDeleteParams, + type FolderCopyParams, + type FolderMoveParams, + type FolderRenameParams, +} from './folders/folders'; +export { + Webhooks, + type VideoTransformationAcceptedEvent, + type VideoTransformationErrorEvent, + type VideoTransformationReadyEvent, + type UnsafeUnwrapWebhookEvent, + type UnwrapWebhookEvent, +} from './webhooks'; diff --git a/src/resources/shared.ts b/src/resources/shared.ts new file mode 100644 index 00000000..1c7f6936 --- /dev/null +++ b/src/resources/shared.ts @@ -0,0 +1,715 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export interface BaseOverlay { + position?: OverlayPosition; + + timing?: OverlayTiming; +} + +export interface ImageOverlay extends BaseOverlay { + /** + * Specifies the relative path to the image used as an overlay. + */ + input: string; + + type: 'image'; + + /** + * The input path can be included in the layer as either `i-{input}` or + * `ie-{base64_encoded_input}`. By default, the SDK determines the appropriate + * format automatically. To always use base64 encoding (`ie-{base64}`), set this + * parameter to `base64`. To always use plain text (`i-{input}`), set it to + * `plain`. + */ + encoding?: 'auto' | 'plain' | 'base64'; + + /** + * Array of transformations to be applied to the overlay image. Supported + * transformations depends on the base/parent asset. + */ + transformation?: Array; +} + +/** + * Specifies an overlay to be applied on the parent image or video. ImageKit + * supports overlays including images, text, videos, subtitles, and solid colors. + */ +export type Overlay = TextOverlay | ImageOverlay | VideoOverlay | SubtitleOverlay | SolidColorOverlay; + +export interface OverlayPosition { + /** + * Specifies the position of the overlay relative to the parent image or video. + * Maps to `lfo` in the URL. + */ + focus?: + | 'center' + | 'top' + | 'left' + | 'bottom' + | 'right' + | 'top_left' + | 'top_right' + | 'bottom_left' + | 'bottom_right'; + + /** + * Specifies the x-coordinate of the top-left corner of the base asset where the + * overlay's top-left corner will be positioned. It also accepts arithmetic + * expressions such as `bw_mul_0.4` or `bw_sub_cw`. Maps to `lx` in the URL. + */ + x?: number | string; + + /** + * Specifies the y-coordinate of the top-left corner of the base asset where the + * overlay's top-left corner will be positioned. It also accepts arithmetic + * expressions such as `bh_mul_0.4` or `bh_sub_ch`. Maps to `ly` in the URL. + */ + y?: number | string; +} + +export interface OverlayTiming { + /** + * Specifies the duration (in seconds) during which the overlay should appear on + * the base video. Accepts a positive number up to two decimal places (e.g., `20` + * or `20.50`) and arithmetic expressions such as `bdu_mul_0.4` or `bdu_sub_idu`. + * Applies only if the base asset is a video. Maps to `ldu` in the URL. + */ + duration?: number | string; + + /** + * Specifies the end time (in seconds) for when the overlay should disappear from + * the base video. If both end and duration are provided, duration is ignored. + * Accepts a positive number up to two decimal places (e.g., `20` or `20.50`) and + * arithmetic expressions such as `bdu_mul_0.4` or `bdu_sub_idu`. Applies only if + * the base asset is a video. Maps to `leo` in the URL. + */ + end?: number | string; + + /** + * Specifies the start time (in seconds) for when the overlay should appear on the + * base video. Accepts a positive number up to two decimal places (e.g., `20` or + * `20.50`) and arithmetic expressions such as `bdu_mul_0.4` or `bdu_sub_idu`. + * Applies only if the base asset is a video. Maps to `lso` in the URL. + */ + start?: number | string; +} + +export interface SolidColorOverlay extends BaseOverlay { + /** + * Specifies the color of the block using an RGB hex code (e.g., `FF0000`), an RGBA + * code (e.g., `FFAABB50`), or a color name (e.g., `red`). If an 8-character value + * is provided, the last two characters represent the opacity level (from `00` for + * 0.00 to `99` for 0.99). + */ + color: string; + + type: 'solidColor'; + + /** + * Control width and height of the solid color overlay. Supported transformations + * depend on the base/parent asset. + */ + transformation?: Array; +} + +export interface SolidColorOverlayTransformation { + /** + * Alpha transparency level + */ + alpha?: number; + + /** + * Background color + */ + background?: string; + + /** + * Gradient effect for the overlay + */ + gradient?: true | string; + + /** + * Height of the solid color overlay + */ + height?: number | string; + + /** + * Corner radius of the solid color overlay + */ + radius?: number | 'max'; + + /** + * Width of the solid color overlay + */ + width?: number | string; +} + +/** + * Options for generating ImageKit URLs with transformations + */ +export interface SrcOptions { + /** + * Accepts a relative or absolute path of the resource. If a relative path is + * provided, it is appended to the `urlEndpoint`. If an absolute path is provided, + * `urlEndpoint` is ignored. + */ + src: string; + + /** + * Get your urlEndpoint from the + * [ImageKit dashboard](https://imagekit.io/dashboard/url-endpoints). + */ + urlEndpoint: string; + + /** + * These are additional query parameters that you want to add to the final URL. + * They can be any query parameters and not necessarily related to ImageKit. This + * is especially useful if you want to add a versioning parameter to your URLs. + */ + queryParameters?: { [key: string]: string }; + + /** + * An array of objects specifying the transformations to be applied in the URL. If + * more than one transformation is specified, they are applied in the order they + * are specified as chained transformations. + */ + transformation?: Array; + + /** + * By default, the transformation string is added as a query parameter in the URL, + * e.g., `?tr=w-100,h-100`. If you want to add the transformation string in the + * path of the URL, set this to `path`. + */ + transformationPosition?: TransformationPosition; +} + +/** + * Available streaming resolutions for adaptive bitrate streaming + */ +export type StreamingResolution = '240' | '360' | '480' | '720' | '1080' | '1440' | '2160'; + +export interface SubtitleOverlay extends BaseOverlay { + /** + * Specifies the relative path to the subtitle file used as an overlay. + */ + input: string; + + type: 'subtitle'; + + /** + * The input path can be included in the layer as either `i-{input}` or + * `ie-{base64_encoded_input}`. By default, the SDK determines the appropriate + * format automatically. To always use base64 encoding (`ie-{base64}`), set this + * parameter to `base64`. To always use plain text (`i-{input}`), set it to + * `plain`. + */ + encoding?: 'auto' | 'plain' | 'base64'; + + /** + * Control styling of the subtitle. + */ + transformation?: Array; +} + +export interface SubtitleOverlayTransformation { + /** + * Background color for subtitles + */ + background?: string; + + /** + * Text color for subtitles + */ + color?: string; + + /** + * Font family for subtitles + */ + fontFamily?: string; + + /** + * Font outline for subtitles + */ + fontOutline?: string; + + /** + * Font shadow for subtitles + */ + fontShadow?: string; + + /** + * Font size for subtitles + */ + fontSize?: number | string; + + /** + * Typography style for subtitles + */ + typography?: 'b' | 'i' | 'b_i'; +} + +export interface TextOverlay extends BaseOverlay { + /** + * Specifies the text to be displayed in the overlay. The SDK automatically handles + * special characters and encoding. + */ + text: string; + + type: 'text'; + + /** + * Text can be included in the layer as either `i-{input}` (plain text) or + * `ie-{base64_encoded_input}` (base64). By default, the SDK selects the + * appropriate format based on the input text. To always use base64 + * (`ie-{base64}`), set this parameter to `base64`. To always use plain text + * (`i-{input}`), set it to `plain`. + */ + encoding?: 'auto' | 'plain' | 'base64'; + + /** + * Control styling of the text overlay. + */ + transformation?: Array; +} + +export interface TextOverlayTransformation { + /** + * Specifies the transparency level of the text overlay. Accepts integers from `1` + * to `9`. + */ + alpha?: number; + + /** + * Specifies the background color of the text overlay. Accepts an RGB hex code, an + * RGBA code, or a color name. + */ + background?: string; + + /** + * Flip the text overlay horizontally, vertically, or both. + */ + flip?: 'h' | 'v' | 'h_v' | 'v_h'; + + /** + * Specifies the font color of the overlaid text. Accepts an RGB hex code (e.g., + * `FF0000`), an RGBA code (e.g., `FFAABB50`), or a color name. + */ + fontColor?: string; + + /** + * Specifies the font family of the overlaid text. Choose from the supported fonts + * list or use a custom font. + */ + fontFamily?: string; + + /** + * Specifies the font size of the overlaid text. Accepts a numeric value or an + * arithmetic expression. + */ + fontSize?: number | string; + + /** + * Specifies the inner alignment of the text when width is more than the text + * length. + */ + innerAlignment?: 'left' | 'right' | 'center'; + + /** + * Specifies the line height of the text overlay. + */ + lineHeight?: number | string; + + /** + * Specifies the padding around the overlaid text. Can be provided as a single + * positive integer or multiple values separated by underscores (following CSS + * shorthand order). Arithmetic expressions are also accepted. + */ + padding?: number | string; + + /** + * Specifies the corner radius of the text overlay. Set to `max` to achieve a + * circular or oval shape. + */ + radius?: number | 'max'; + + /** + * Specifies the rotation angle of the text overlay. Accepts a numeric value for + * clockwise rotation or a string prefixed with "N" for counter-clockwise rotation. + */ + rotation?: number | string; + + /** + * Specifies the typography style of the text. Supported values: `b` for bold, `i` + * for italics, and `b_i` for bold with italics. + */ + typography?: 'b' | 'i' | 'b_i'; + + /** + * Specifies the maximum width (in pixels) of the overlaid text. The text wraps + * automatically, and arithmetic expressions (e.g., `bw_mul_0.2` or `bh_div_2`) are + * supported. Useful when used in conjunction with the `background`. + */ + width?: number | string; +} + +/** + * The SDK provides easy-to-use names for transformations. These names are + * converted to the corresponding transformation string before being added to the + * URL. SDKs are updated regularly to support new transformations. If you want to + * use a transformation that is not supported by the SDK, You can use the `raw` + * parameter to pass the transformation string directly. + */ +export interface Transformation { + /** + * Uses AI to change the background. Provide a text prompt or a base64-encoded + * prompt, e.g., `prompt-snow road` or `prompte-[urlencoded_base64_encoded_text]`. + * Not supported inside overlay. + */ + aiChangeBackground?: string; + + /** + * Adds an AI-based drop shadow around a foreground object on a transparent or + * removed background. Optionally, control the direction, elevation, and saturation + * of the light source (e.g., `az-45` to change light direction). Pass `true` for + * the default drop shadow, or provide a string for a custom drop shadow. Supported + * inside overlay. + */ + aiDropShadow?: true | string; + + /** + * Applies ImageKit's in-house background removal. Supported inside overlay. + */ + aiRemoveBackground?: true; + + /** + * Uses third-party background removal. Note: It is recommended to use + * aiRemoveBackground, ImageKit's in-house solution, which is more cost-effective. + * Supported inside overlay. + */ + aiRemoveBackgroundExternal?: true; + + /** + * Performs AI-based retouching to improve faces or product shots. Not supported + * inside overlay. + */ + aiRetouch?: true; + + /** + * Upscales images beyond their original dimensions using AI. Not supported inside + * overlay. + */ + aiUpscale?: true; + + /** + * Generates a variation of an image using AI. This produces a new image with + * slight variations from the original, such as changes in color, texture, and + * other visual elements, while preserving the structure and essence of the + * original image. Not supported inside overlay. + */ + aiVariation?: true; + + /** + * Specifies the aspect ratio for the output, e.g., "ar-4-3". Typically used with + * either width or height (but not both). For example: aspectRatio = `4:3`, `4_3`, + * or an expression like `iar_div_2`. + */ + aspectRatio?: number | string; + + /** + * Specifies the audio codec, e.g., `aac`, `opus`, or `none`. + */ + audioCodec?: 'aac' | 'opus' | 'none'; + + /** + * Specifies the background to be used in conjunction with certain cropping + * strategies when resizing an image. + * + * - A solid color: e.g., `red`, `F3F3F3`, `AAFF0010`. + * - A blurred background: e.g., `blurred`, `blurred_25_N15`, etc. + * - Expand the image boundaries using generative fill: `genfill`. Not supported + * inside overlay. Optionally, control the background scene by passing a text + * prompt: `genfill[:-prompt-${text}]` or + * `genfill[:-prompte-${urlencoded_base64_encoded_text}]`. + */ + background?: string; + + /** + * Specifies the Gaussian blur level. Accepts an integer value between 1 and 100, + * or an expression like `bl-10`. + */ + blur?: number; + + /** + * Adds a border to the output media. Accepts a string in the format + * `_` (e.g., `5_FFF000` for a 5px yellow border), or an + * expression like `ih_div_20_FF00FF`. + */ + border?: string; + + /** + * Indicates whether the output image should retain the original color profile. + */ + colorProfile?: boolean; + + /** + * Automatically enhances the contrast of an image (contrast stretch). + */ + contrastStretch?: true; + + /** + * Crop modes for image resizing + */ + crop?: 'force' | 'at_max' | 'at_max_enlarge' | 'at_least' | 'maintain_ratio'; + + /** + * Additional crop modes for image resizing + */ + cropMode?: 'pad_resize' | 'extract' | 'pad_extract'; + + /** + * Specifies a fallback image if the resource is not found, e.g., a URL or file + * path. + */ + defaultImage?: string; + + /** + * Accepts values between 0.1 and 5, or `auto` for automatic device pixel ratio + * (DPR) calculation. + */ + dpr?: number; + + /** + * Specifies the duration (in seconds) for trimming videos, e.g., `5` or `10.5`. + * Typically used with startOffset to indicate the length from the start offset. + * Arithmetic expressions are supported. + */ + duration?: number | string; + + /** + * Specifies the end offset (in seconds) for trimming videos, e.g., `5` or `10.5`. + * Typically used with startOffset to define a time window. Arithmetic expressions + * are supported. + */ + endOffset?: number | string; + + /** + * Flips or mirrors an image either horizontally, vertically, or both. Acceptable + * values: `h` (horizontal), `v` (vertical), `h_v` (horizontal and vertical), or + * `v_h`. + */ + flip?: 'h' | 'v' | 'h_v' | 'v_h'; + + /** + * This parameter can be used with pad resize, maintain ratio, or extract crop to + * modify the padding or cropping behavior. + */ + focus?: string; + + /** + * Specifies the output format for images or videos, e.g., `jpg`, `png`, `webp`, + * `mp4`, or `auto`. You can also pass `orig` for images to return the original + * format. ImageKit automatically delivers images and videos in the optimal format + * based on device support unless overridden by the dashboard settings or the + * format parameter. + */ + format?: 'auto' | 'webp' | 'jpg' | 'jpeg' | 'png' | 'gif' | 'svg' | 'mp4' | 'webm' | 'avif' | 'orig'; + + /** + * Creates a linear gradient with two colors. Pass `true` for a default gradient, + * or provide a string for a custom gradient. + */ + gradient?: true | string; + + /** + * Enables a grayscale effect for images. + */ + grayscale?: true; + + /** + * Specifies the height of the output. If a value between 0 and 1 is provided, it + * is treated as a percentage (e.g., `0.5` represents 50% of the original height). + * You can also supply arithmetic expressions (e.g., `ih_mul_0.5`). + */ + height?: number | string; + + /** + * Specifies whether the output image (in JPEG or PNG) should be compressed + * losslessly. + */ + lossless?: boolean; + + /** + * By default, ImageKit removes all metadata during automatic image compression. + * Set this to true to preserve metadata. + */ + metadata?: boolean; + + /** + * Named transformation reference + */ + named?: string; + + /** + * Specifies the opacity level of the output image. + */ + opacity?: number; + + /** + * If set to true, serves the original file without applying any transformations. + */ + original?: boolean; + + /** + * Specifies an overlay to be applied on the parent image or video. ImageKit + * supports overlays including images, text, videos, subtitles, and solid colors. + */ + overlay?: Overlay; + + /** + * Extracts a specific page or frame from multi-page or layered files (PDF, PSD, + * AI). For example, specify by number (e.g., `2`), a range (e.g., `3-4` for the + * 2nd and 3rd layers), or by name (e.g., `name-layer-4` for a PSD layer). + */ + page?: number | string; + + /** + * Specifies whether the output JPEG image should be rendered progressively. + * Progressive loading begins with a low-quality, pixelated version of the full + * image, which gradually improves to provide a faster perceived load time. + */ + progressive?: boolean; + + /** + * Specifies the quality of the output image for lossy formats such as JPEG, WebP, + * and AVIF. A higher quality value results in a larger file size with better + * quality, while a lower value produces a smaller file size with reduced quality. + */ + quality?: number; + + /** + * Specifies the corner radius for rounded corners (e.g., 20) or `max` for + * circular/oval shapes. + */ + radius?: number | 'max'; + + /** + * Pass any transformation not directly supported by the SDK. This transformation + * string is appended to the URL as provided. + */ + raw?: string; + + /** + * Specifies the rotation angle in degrees. Positive values rotate the image + * clockwise; you can also use, for example, `N40` for counterclockwise rotation or + * `auto` to use the orientation specified in the image's EXIF data. For videos, + * only the following values are supported: 0, 90, 180, 270, or 360. + */ + rotation?: number | string; + + /** + * Adds a shadow beneath solid objects in an image with a transparent background. + * For AI-based drop shadows, refer to aiDropShadow. Pass `true` for a default + * shadow, or provide a string for a custom shadow. + */ + shadow?: true | string; + + /** + * Sharpens the input image, highlighting edges and finer details. Pass `true` for + * default sharpening, or provide a numeric value for custom sharpening. + */ + sharpen?: true | number; + + /** + * Specifies the start offset (in seconds) for trimming videos, e.g., `5` or + * `10.5`. Arithmetic expressions are also supported. + */ + startOffset?: number | string; + + /** + * An array of resolutions for adaptive bitrate streaming, e.g., [`240`, `360`, + * `480`, `720`, `1080`]. + */ + streamingResolutions?: Array; + + /** + * Useful for images with a solid or nearly solid background and a central object. + * This parameter trims the background, leaving only the central object in the + * output image. + */ + trim?: true | number; + + /** + * Applies Unsharp Masking (USM), an image sharpening technique. Pass `true` for a + * default unsharp mask, or provide a string for a custom unsharp mask. + */ + unsharpMask?: true | string; + + /** + * Specifies the video codec, e.g., `h264`, `vp9`, `av1`, or `none`. + */ + videoCodec?: 'h264' | 'vp9' | 'av1' | 'none'; + + /** + * Specifies the width of the output. If a value between 0 and 1 is provided, it is + * treated as a percentage (e.g., `0.4` represents 40% of the original width). You + * can also supply arithmetic expressions (e.g., `iw_div_2`). + */ + width?: number | string; + + /** + * Focus using cropped image coordinates - X coordinate + */ + x?: number | string; + + /** + * Focus using cropped image coordinates - X center coordinate + */ + xCenter?: number | string; + + /** + * Focus using cropped image coordinates - Y coordinate + */ + y?: number | string; + + /** + * Focus using cropped image coordinates - Y center coordinate + */ + yCenter?: number | string; + + /** + * Accepts a numeric value that determines how much to zoom in or out of the + * cropped area. It should be used in conjunction with fo-face or fo-. + */ + zoom?: number; +} + +/** + * By default, the transformation string is added as a query parameter in the URL, + * e.g., `?tr=w-100,h-100`. If you want to add the transformation string in the + * path of the URL, set this to `path`. + */ +export type TransformationPosition = 'path' | 'query'; + +export interface VideoOverlay extends BaseOverlay { + /** + * Specifies the relative path to the video used as an overlay. + */ + input: string; + + type: 'video'; + + /** + * The input path can be included in the layer as either `i-{input}` or + * `ie-{base64_encoded_input}`. By default, the SDK determines the appropriate + * format automatically. To always use base64 encoding (`ie-{base64}`), set this + * parameter to `base64`. To always use plain text (`i-{input}`), set it to + * `plain`. + */ + encoding?: 'auto' | 'plain' | 'base64'; + + /** + * Array of transformation to be applied to the overlay video. Except + * `streamingResolutions`, all other video transformations are supported. + */ + transformation?: Array; +} diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts new file mode 100644 index 00000000..770f5a12 --- /dev/null +++ b/src/resources/webhooks.ts @@ -0,0 +1,302 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { Webhook } from 'standardwebhooks'; + +export class Webhooks extends APIResource { + unsafeUnwrap(body: string): UnsafeUnwrapWebhookEvent { + return JSON.parse(body) as UnsafeUnwrapWebhookEvent; + } + + unwrap( + body: string, + { headers, key }: { headers: Record; key?: string }, + ): UnwrapWebhookEvent { + if (headers !== undefined) { + const keyStr: string | null = key === undefined ? this._client.privateAPIKey : key; + if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); + const wh = new Webhook(keyStr); + wh.verify(body, headers); + } + return JSON.parse(body) as UnwrapWebhookEvent; + } +} + +export interface VideoTransformationAcceptedEvent { + /** + * Unique identifier for the event. + */ + id: string; + + created_at: string; + + data: VideoTransformationAcceptedEvent.Data; + + request: VideoTransformationAcceptedEvent.Request; + + type: 'video.transformation.accepted'; +} + +export namespace VideoTransformationAcceptedEvent { + export interface Data { + asset: Data.Asset; + + transformation: Data.Transformation; + } + + export namespace Data { + export interface Asset { + /** + * Source asset URL. + */ + url: string; + } + + export interface Transformation { + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + + options?: Transformation.Options; + } + + export namespace Transformation { + export interface Options { + audio_codec?: 'aac' | 'opus'; + + auto_rotate?: boolean; + + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + + quality?: number; + + stream_protocol?: 'HLS' | 'DASH'; + + variants?: Array; + + video_codec?: 'h264' | 'vp9'; + } + } + } + + export interface Request { + /** + * URL of the submitted request. + */ + url: string; + + /** + * Unique ID for the originating request. + */ + x_request_id: string; + + /** + * User-Agent header of the originating request. + */ + user_agent?: string; + } +} + +export interface VideoTransformationErrorEvent { + /** + * Unique identifier for the event. + */ + id: string; + + created_at: string; + + data: VideoTransformationErrorEvent.Data; + + request: VideoTransformationErrorEvent.Request; + + type: 'video.transformation.error'; +} + +export namespace VideoTransformationErrorEvent { + export interface Data { + asset: Data.Asset; + + transformation: Data.Transformation; + } + + export namespace Data { + export interface Asset { + /** + * Source asset URL. + */ + url: string; + } + + export interface Transformation { + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + + error?: Transformation.Error; + + options?: Transformation.Options; + } + + export namespace Transformation { + export interface Error { + reason: 'encoding_failed' | 'download_failed' | 'internal_server_error'; + } + + export interface Options { + audio_codec?: 'aac' | 'opus'; + + auto_rotate?: boolean; + + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + + quality?: number; + + stream_protocol?: 'HLS' | 'DASH'; + + variants?: Array; + + video_codec?: 'h264' | 'vp9'; + } + } + } + + export interface Request { + /** + * URL of the submitted request. + */ + url: string; + + /** + * Unique ID for the originating request. + */ + x_request_id: string; + + /** + * User-Agent header of the originating request. + */ + user_agent?: string; + } +} + +export interface VideoTransformationReadyEvent { + /** + * Unique identifier for the event. + */ + id: string; + + created_at: string; + + data: VideoTransformationReadyEvent.Data; + + request: VideoTransformationReadyEvent.Request; + + type: 'video.transformation.ready'; + + timings?: VideoTransformationReadyEvent.Timings; +} + +export namespace VideoTransformationReadyEvent { + export interface Data { + asset: Data.Asset; + + transformation: Data.Transformation; + } + + export namespace Data { + export interface Asset { + /** + * Source asset URL. + */ + url: string; + } + + export interface Transformation { + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + + options?: Transformation.Options; + + output?: Transformation.Output; + } + + export namespace Transformation { + export interface Options { + audio_codec?: 'aac' | 'opus'; + + auto_rotate?: boolean; + + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + + quality?: number; + + stream_protocol?: 'HLS' | 'DASH'; + + variants?: Array; + + video_codec?: 'h264' | 'vp9'; + } + + export interface Output { + url: string; + + video_metadata?: Output.VideoMetadata; + } + + export namespace Output { + export interface VideoMetadata { + bitrate: number; + + duration: number; + + height: number; + + width: number; + } + } + } + } + + export interface Request { + /** + * URL of the submitted request. + */ + url: string; + + /** + * Unique ID for the originating request. + */ + x_request_id: string; + + /** + * User-Agent header of the originating request. + */ + user_agent?: string; + } + + export interface Timings { + /** + * Milliseconds spent downloading the source. + */ + download_duration?: number; + + /** + * Milliseconds spent encoding. + */ + encoding_duration?: number; + } +} + +export type UnsafeUnwrapWebhookEvent = + | VideoTransformationAcceptedEvent + | VideoTransformationReadyEvent + | VideoTransformationErrorEvent; + +export type UnwrapWebhookEvent = + | VideoTransformationAcceptedEvent + | VideoTransformationReadyEvent + | VideoTransformationErrorEvent; + +export declare namespace Webhooks { + export { + type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, + type VideoTransformationErrorEvent as VideoTransformationErrorEvent, + type VideoTransformationReadyEvent as VideoTransformationReadyEvent, + type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, + type UnwrapWebhookEvent as UnwrapWebhookEvent, + }; +} diff --git a/src/uploads.ts b/src/uploads.ts new file mode 100644 index 00000000..b2ef6471 --- /dev/null +++ b/src/uploads.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/uploads instead */ +export * from './core/uploads'; diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 00000000..55a1a527 --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const VERSION = '0.0.1-alpha.0'; diff --git a/test-e2e.sh b/test-e2e.sh deleted file mode 100644 index e9aa713f..00000000 --- a/test-e2e.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -npm pack -cd tests/e2e -export YARN_CACHE_FOLDER=.cache -cd node-js -rm -rf .cache -yarn init --yes -echo "Installing local bundle from TAR in NodeJS project" -yarn add ../../../imagekit*.tgz -node index.js;test_result=$? -echo $test_result -if [ "$test_result" != "0" ]; then - printf '%s\n' "Final bundle not working in NodeJS project" >&2 - exit 1 -fi -echo "Final bundle working in NodeJS project" - -cd ../typescript -rm -rf .cache -yarn init --yes -yarn add typescript --dev -echo "Installing local bundle from TAR in Typescript project" -yarn add ../../../imagekit*.tgz -npx tsc && node index.js;test_result=$? -echo $test_result -if [ "$test_result" != "0" ]; then - printf '%s\n' "Final bundle not working in Typescript project" >&2 - exit 1 -fi -echo "Final bundle working in Typescript project" - -rm -rf ../../../imagekit*.tgz \ No newline at end of file diff --git a/tests/api-resources/accounts/origins.test.ts b/tests/api-resources/accounts/origins.test.ts new file mode 100644 index 00000000..6881e301 --- /dev/null +++ b/tests/api-resources/accounts/origins.test.ts @@ -0,0 +1,119 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource origins', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.accounts.origins.create({ + origin: { + accessKey: 'AKIATEST123', + bucket: 'test-bucket', + name: 'My S3 Origin', + secretKey: 'secrettest123', + type: 'S3', + }, + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.accounts.origins.create({ + origin: { + accessKey: 'AKIATEST123', + bucket: 'test-bucket', + name: 'My S3 Origin', + secretKey: 'secrettest123', + type: 'S3', + baseUrlForCanonicalHeader: 'https://cdn.example.com', + includeCanonicalHeader: false, + prefix: 'images', + }, + }); + }); + + // Prism tests are disabled + test.skip('update: only required params', async () => { + const responsePromise = client.accounts.origins.update('id', { + origin: { + accessKey: 'AKIATEST123', + bucket: 'test-bucket', + name: 'My S3 Origin', + secretKey: 'secrettest123', + type: 'S3', + }, + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: required and optional params', async () => { + const response = await client.accounts.origins.update('id', { + origin: { + accessKey: 'AKIATEST123', + bucket: 'test-bucket', + name: 'My S3 Origin', + secretKey: 'secrettest123', + type: 'S3', + baseUrlForCanonicalHeader: 'https://cdn.example.com', + includeCanonicalHeader: false, + prefix: 'images', + }, + }); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.accounts.origins.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete', async () => { + const responsePromise = client.accounts.origins.delete('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.accounts.origins.get('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/accounts/url-endpoints.test.ts b/tests/api-resources/accounts/url-endpoints.test.ts new file mode 100644 index 00000000..4f09652b --- /dev/null +++ b/tests/api-resources/accounts/url-endpoints.test.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource urlEndpoints', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.accounts.urlEndpoints.create({ description: 'My custom URL endpoint' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.accounts.urlEndpoints.create({ + description: 'My custom URL endpoint', + origins: ['origin-id-1'], + urlPrefix: 'product-images', + urlRewriter: { type: 'CLOUDINARY', preserveAssetDeliveryTypes: true }, + }); + }); + + // Prism tests are disabled + test.skip('update: only required params', async () => { + const responsePromise = client.accounts.urlEndpoints.update('id', { + description: 'My custom URL endpoint', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: required and optional params', async () => { + const response = await client.accounts.urlEndpoints.update('id', { + description: 'My custom URL endpoint', + origins: ['origin-id-1'], + urlPrefix: 'product-images', + urlRewriter: { type: 'CLOUDINARY', preserveAssetDeliveryTypes: true }, + }); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.accounts.urlEndpoints.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete', async () => { + const responsePromise = client.accounts.urlEndpoints.delete('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.accounts.urlEndpoints.get('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/accounts/usage.test.ts b/tests/api-resources/accounts/usage.test.ts new file mode 100644 index 00000000..5e83c3c5 --- /dev/null +++ b/tests/api-resources/accounts/usage.test.ts @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource usage', () => { + // Prism tests are disabled + test.skip('get: only required params', async () => { + const responsePromise = client.accounts.usage.get({ endDate: '2019-12-27', startDate: '2019-12-27' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('get: required and optional params', async () => { + const response = await client.accounts.usage.get({ endDate: '2019-12-27', startDate: '2019-12-27' }); + }); +}); diff --git a/tests/api-resources/assets.test.ts b/tests/api-resources/assets.test.ts new file mode 100644 index 00000000..a67f9a34 --- /dev/null +++ b/tests/api-resources/assets.test.ts @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource assets', () => { + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.assets.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.assets.list( + { + fileType: 'all', + limit: 1, + path: 'path', + searchQuery: 'searchQuery', + skip: 0, + sort: 'ASC_NAME', + type: 'file', + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(ImageKit.NotFoundError); + }); +}); diff --git a/tests/api-resources/beta/v2/files.test.ts b/tests/api-resources/beta/v2/files.test.ts new file mode 100644 index 00000000..1012110e --- /dev/null +++ b/tests/api-resources/beta/v2/files.test.ts @@ -0,0 +1,70 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit, { toFile } from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource files', () => { + // Prism tests are disabled + test.skip('upload: only required params', async () => { + const responsePromise = client.beta.v2.files.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + fileName: 'fileName', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('upload: required and optional params', async () => { + const response = await client.beta.v2.files.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + fileName: 'fileName', + token: 'token', + checks: '"request.folder" : "marketing/"\n', + customCoordinates: 'customCoordinates', + customMetadata: { brand: 'bar', color: 'bar' }, + description: 'Running shoes', + extensions: [ + { + name: 'remove-bg', + options: { + add_shadow: true, + bg_color: 'bg_color', + bg_image_url: 'bg_image_url', + semitransparency: true, + }, + }, + { maxTags: 5, minConfidence: 95, name: 'google-auto-tagging' }, + { name: 'ai-auto-description' }, + ], + folder: 'folder', + isPrivateFile: true, + isPublished: true, + overwriteAITags: true, + overwriteCustomMetadata: true, + overwriteFile: true, + overwriteTags: true, + responseFields: ['tags', 'customCoordinates', 'isPrivateFile'], + tags: ['t-shirt', 'round-neck', 'men'], + transformation: { + post: [ + { type: 'thumbnail', value: 'w-150,h-150' }, + { protocol: 'dash', type: 'abs', value: 'sr-240_360_480_720_1080' }, + ], + pre: 'w-300,h-300,q-80', + }, + useUniqueFileName: true, + webhookUrl: 'https://example.com', + }); + }); +}); diff --git a/tests/api-resources/cache/invalidation.test.ts b/tests/api-resources/cache/invalidation.test.ts new file mode 100644 index 00000000..0354e08e --- /dev/null +++ b/tests/api-resources/cache/invalidation.test.ts @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource invalidation', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.cache.invalidation.create({ + url: 'https://ik.imagekit.io/your_imagekit_id/default-image.jpg', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.cache.invalidation.create({ + url: 'https://ik.imagekit.io/your_imagekit_id/default-image.jpg', + }); + }); + + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.cache.invalidation.get('requestId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/custom-metadata-fields.test.ts b/tests/api-resources/custom-metadata-fields.test.ts new file mode 100644 index 00000000..9ebf4d3a --- /dev/null +++ b/tests/api-resources/custom-metadata-fields.test.ts @@ -0,0 +1,112 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource customMetadataFields', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.customMetadataFields.create({ + label: 'price', + name: 'price', + schema: { type: 'Number' }, + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.customMetadataFields.create({ + label: 'price', + name: 'price', + schema: { + type: 'Number', + defaultValue: 'string', + isValueRequired: true, + maxLength: 0, + maxValue: 3000, + minLength: 0, + minValue: 1000, + selectOptions: ['small', 'medium', 'large', 30, 40, true], + }, + }); + }); + + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.customMetadataFields.update('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.customMetadataFields.update( + 'id', + { + label: 'price', + schema: { + defaultValue: 'string', + isValueRequired: true, + maxLength: 0, + maxValue: 3000, + minLength: 0, + minValue: 1000, + selectOptions: ['small', 'medium', 'large', 30, 40, true], + }, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(ImageKit.NotFoundError); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.customMetadataFields.list(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('list: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.customMetadataFields.list({ includeDeleted: true }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(ImageKit.NotFoundError); + }); + + // Prism tests are disabled + test.skip('delete', async () => { + const responsePromise = client.customMetadataFields.delete('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/files/bulk.test.ts b/tests/api-resources/files/bulk.test.ts new file mode 100644 index 00000000..823b35b6 --- /dev/null +++ b/tests/api-resources/files/bulk.test.ts @@ -0,0 +1,101 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource bulk', () => { + // Prism tests are disabled + test.skip('delete: only required params', async () => { + const responsePromise = client.files.bulk.delete({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete: required and optional params', async () => { + const response = await client.files.bulk.delete({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + }); + }); + + // Prism tests are disabled + test.skip('addTags: only required params', async () => { + const responsePromise = client.files.bulk.addTags({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + tags: ['t-shirt', 'round-neck', 'sale2019'], + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('addTags: required and optional params', async () => { + const response = await client.files.bulk.addTags({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + tags: ['t-shirt', 'round-neck', 'sale2019'], + }); + }); + + // Prism tests are disabled + test.skip('removeAITags: only required params', async () => { + const responsePromise = client.files.bulk.removeAITags({ + AITags: ['t-shirt', 'round-neck', 'sale2019'], + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('removeAITags: required and optional params', async () => { + const response = await client.files.bulk.removeAITags({ + AITags: ['t-shirt', 'round-neck', 'sale2019'], + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + }); + }); + + // Prism tests are disabled + test.skip('removeTags: only required params', async () => { + const responsePromise = client.files.bulk.removeTags({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + tags: ['t-shirt', 'round-neck', 'sale2019'], + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('removeTags: required and optional params', async () => { + const response = await client.files.bulk.removeTags({ + fileIds: ['598821f949c0a938d57563bd', '598821f949c0a938d57563be'], + tags: ['t-shirt', 'round-neck', 'sale2019'], + }); + }); +}); diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts new file mode 100644 index 00000000..8969d90b --- /dev/null +++ b/tests/api-resources/files/files.test.ts @@ -0,0 +1,215 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit, { toFile } from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource files', () => { + // Prism tests are disabled + test.skip('update', async () => { + const responsePromise = client.files.update('fileId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('update: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.files.update( + 'fileId', + { + update: { + customCoordinates: '10,10,100,100', + customMetadata: { brand: 'bar', color: 'bar' }, + description: 'description', + extensions: [ + { + name: 'remove-bg', + options: { + add_shadow: true, + bg_color: 'bg_color', + bg_image_url: 'bg_image_url', + semitransparency: true, + }, + }, + { maxTags: 10, minConfidence: 80, name: 'google-auto-tagging' }, + { maxTags: 10, minConfidence: 80, name: 'aws-auto-tagging' }, + { name: 'ai-auto-description' }, + ], + removeAITags: ['car', 'vehicle', 'motorsports'], + tags: ['tag1', 'tag2'], + webhookUrl: 'https://webhook.site/0d6b6c7a-8e5a-4b3a-8b7c-0d6b6c7a8e5a', + }, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(ImageKit.NotFoundError); + }); + + // Prism tests are disabled + test.skip('delete', async () => { + const responsePromise = client.files.delete('fileId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('copy: only required params', async () => { + const responsePromise = client.files.copy({ + destinationPath: '/folder/to/copy/into/', + sourceFilePath: '/path/to/file.jpg', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('copy: required and optional params', async () => { + const response = await client.files.copy({ + destinationPath: '/folder/to/copy/into/', + sourceFilePath: '/path/to/file.jpg', + includeFileVersions: false, + }); + }); + + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.files.get('fileId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('move: only required params', async () => { + const responsePromise = client.files.move({ + destinationPath: '/folder/to/move/into/', + sourceFilePath: '/path/to/file.jpg', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('move: required and optional params', async () => { + const response = await client.files.move({ + destinationPath: '/folder/to/move/into/', + sourceFilePath: '/path/to/file.jpg', + }); + }); + + // Prism tests are disabled + test.skip('rename: only required params', async () => { + const responsePromise = client.files.rename({ + filePath: '/path/to/file.jpg', + newFileName: 'newFileName.jpg', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('rename: required and optional params', async () => { + const response = await client.files.rename({ + filePath: '/path/to/file.jpg', + newFileName: 'newFileName.jpg', + purgeCache: true, + }); + }); + + // Prism tests are disabled + test.skip('upload: only required params', async () => { + const responsePromise = client.files.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + fileName: 'fileName', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('upload: required and optional params', async () => { + const response = await client.files.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + fileName: 'fileName', + token: 'token', + checks: '"request.folder" : "marketing/"\n', + customCoordinates: 'customCoordinates', + customMetadata: { brand: 'bar', color: 'bar' }, + description: 'Running shoes', + expire: 0, + extensions: [ + { + name: 'remove-bg', + options: { + add_shadow: true, + bg_color: 'bg_color', + bg_image_url: 'bg_image_url', + semitransparency: true, + }, + }, + { maxTags: 5, minConfidence: 95, name: 'google-auto-tagging' }, + { name: 'ai-auto-description' }, + ], + folder: 'folder', + isPrivateFile: true, + isPublished: true, + overwriteAITags: true, + overwriteCustomMetadata: true, + overwriteFile: true, + overwriteTags: true, + publicKey: 'publicKey', + responseFields: ['tags', 'customCoordinates', 'isPrivateFile'], + signature: 'signature', + tags: ['t-shirt', 'round-neck', 'men'], + transformation: { + post: [ + { type: 'thumbnail', value: 'w-150,h-150' }, + { protocol: 'dash', type: 'abs', value: 'sr-240_360_480_720_1080' }, + ], + pre: 'w-300,h-300,q-80', + }, + useUniqueFileName: true, + webhookUrl: 'https://example.com', + }); + }); +}); diff --git a/tests/api-resources/files/metadata.test.ts b/tests/api-resources/files/metadata.test.ts new file mode 100644 index 00000000..8b2c74cc --- /dev/null +++ b/tests/api-resources/files/metadata.test.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource metadata', () => { + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.files.metadata.get('fileId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('getFromURL: only required params', async () => { + const responsePromise = client.files.metadata.getFromURL({ url: 'https://example.com' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('getFromURL: required and optional params', async () => { + const response = await client.files.metadata.getFromURL({ url: 'https://example.com' }); + }); +}); diff --git a/tests/api-resources/files/versions.test.ts b/tests/api-resources/files/versions.test.ts new file mode 100644 index 00000000..24800f8c --- /dev/null +++ b/tests/api-resources/files/versions.test.ts @@ -0,0 +1,74 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource versions', () => { + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.files.versions.list('fileId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete: only required params', async () => { + const responsePromise = client.files.versions.delete('versionId', { fileId: 'fileId' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete: required and optional params', async () => { + const response = await client.files.versions.delete('versionId', { fileId: 'fileId' }); + }); + + // Prism tests are disabled + test.skip('get: only required params', async () => { + const responsePromise = client.files.versions.get('versionId', { fileId: 'fileId' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('get: required and optional params', async () => { + const response = await client.files.versions.get('versionId', { fileId: 'fileId' }); + }); + + // Prism tests are disabled + test.skip('restore: only required params', async () => { + const responsePromise = client.files.versions.restore('versionId', { fileId: 'fileId' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('restore: required and optional params', async () => { + const response = await client.files.versions.restore('versionId', { fileId: 'fileId' }); + }); +}); diff --git a/tests/api-resources/folders/folders.test.ts b/tests/api-resources/folders/folders.test.ts new file mode 100644 index 00000000..e42b6414 --- /dev/null +++ b/tests/api-resources/folders/folders.test.ts @@ -0,0 +1,122 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource folders', () => { + // Prism tests are disabled + test.skip('create: only required params', async () => { + const responsePromise = client.folders.create({ + folderName: 'summer', + parentFolderPath: '/product/images/', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('create: required and optional params', async () => { + const response = await client.folders.create({ + folderName: 'summer', + parentFolderPath: '/product/images/', + }); + }); + + // Prism tests are disabled + test.skip('delete: only required params', async () => { + const responsePromise = client.folders.delete({ folderPath: '/folder/to/delete/' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('delete: required and optional params', async () => { + const response = await client.folders.delete({ folderPath: '/folder/to/delete/' }); + }); + + // Prism tests are disabled + test.skip('copy: only required params', async () => { + const responsePromise = client.folders.copy({ + destinationPath: '/path/of/destination/folder', + sourceFolderPath: '/path/of/source/folder', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('copy: required and optional params', async () => { + const response = await client.folders.copy({ + destinationPath: '/path/of/destination/folder', + sourceFolderPath: '/path/of/source/folder', + includeVersions: true, + }); + }); + + // Prism tests are disabled + test.skip('move: only required params', async () => { + const responsePromise = client.folders.move({ + destinationPath: '/path/of/destination/folder', + sourceFolderPath: '/path/of/source/folder', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('move: required and optional params', async () => { + const response = await client.folders.move({ + destinationPath: '/path/of/destination/folder', + sourceFolderPath: '/path/of/source/folder', + }); + }); + + // Prism tests are disabled + test.skip('rename: only required params', async () => { + const responsePromise = client.folders.rename({ + folderPath: '/path/of/folder', + newFolderName: 'new-folder-name', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // Prism tests are disabled + test.skip('rename: required and optional params', async () => { + const response = await client.folders.rename({ + folderPath: '/path/of/folder', + newFolderName: 'new-folder-name', + purgeCache: true, + }); + }); +}); diff --git a/tests/api-resources/folders/job.test.ts b/tests/api-resources/folders/job.test.ts new file mode 100644 index 00000000..3e698206 --- /dev/null +++ b/tests/api-resources/folders/job.test.ts @@ -0,0 +1,23 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource job', () => { + // Prism tests are disabled + test.skip('get', async () => { + const responsePromise = client.folders.job.get('jobId'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); +}); diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts new file mode 100644 index 00000000..f3b1af6e --- /dev/null +++ b/tests/api-resources/webhooks.test.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Webhook } from 'standardwebhooks'; + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource webhooks', () => { + test.skip('unwrap', async () => { + const key = 'whsec_c2VjcmV0Cg=='; + const payload = + '{"id":"id","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"},"type":"video.transformation.accepted"}'; + const msgID = '1'; + const timestamp = new Date(); + const wh = new Webhook(key); + const signature = wh.sign(msgID, timestamp, payload); + const headers: Record = { + 'webhook-signature': signature, + 'webhook-id': msgID, + 'webhook-timestamp': String(Math.floor(timestamp.getTime() / 1000)), + }; + client.webhooks.unwrap(payload, { headers, key }); + expect(() => { + const wrongKey = 'whsec_aaaaaaaaaa=='; + client.webhooks.unwrap(payload, { headers, key: wrongKey }); + }).toThrow('No matching signature found'); + expect(() => { + const badSig = wh.sign(msgID, timestamp, 'some other payload'); + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-signature': badSig }, key }); + }).toThrow('No matching signature found'); + expect(() => { + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-timestamp': '5' }, key }); + }).toThrow('Message timestamp too old'); + expect(() => { + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-id': 'wrong' }, key }); + }).toThrow('No matching signature found'); + }); +}); diff --git a/tests/base64.test.ts b/tests/base64.test.ts new file mode 100644 index 00000000..51910051 --- /dev/null +++ b/tests/base64.test.ts @@ -0,0 +1,80 @@ +import { fromBase64, toBase64 } from '@imagekit/nodejs/internal/utils/base64'; + +describe.each(['Buffer', 'atob'])('with %s', (mode) => { + let originalBuffer: BufferConstructor; + beforeAll(() => { + if (mode === 'atob') { + originalBuffer = globalThis.Buffer; + // @ts-expect-error Can't assign undefined to BufferConstructor + delete globalThis.Buffer; + } + }); + afterAll(() => { + if (mode === 'atob') { + globalThis.Buffer = originalBuffer; + } + }); + test('toBase64', () => { + const testCases = [ + { + input: 'hello world', + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + expected: 'aGVsbG8gd29ybGQ=', + }, + { + input: undefined, + expected: '', + }, + { + input: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + }, + { + input: '✓', + expected: '4pyT', + }, + { + input: new Uint8Array([226, 156, 147]), + expected: '4pyT', + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(toBase64(input)).toBe(expected); + }); + }); + + test('fromBase64', () => { + const testCases = [ + { + input: 'aGVsbG8gd29ybGQ=', + expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + }, + { + input: '', + expected: new Uint8Array([]), + }, + { + input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', + expected: new Uint8Array([ + 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, + 193, 71, + ]), + }, + { + input: '4pyT', + expected: new Uint8Array([226, 156, 147]), + }, + ]; + + testCases.forEach(({ input, expected }) => { + expect(fromBase64(input)).toEqual(expected); + }); + }); +}); diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts new file mode 100644 index 00000000..818d0b89 --- /dev/null +++ b/tests/buildHeaders.test.ts @@ -0,0 +1,88 @@ +import { inspect } from 'node:util'; +import { buildHeaders, type HeadersLike, type NullableHeaders } from '@imagekit/nodejs/internal/headers'; + +function inspectNullableHeaders(headers: NullableHeaders) { + return `NullableHeaders {${[ + ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), + ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), + ].join(', ')} }`; +} + +describe('buildHeaders', () => { + const cases: [HeadersLike[], string][] = [ + [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': undefined, + }, + ], + `NullableHeaders { 'content-type': 'text/plain' }`, + ], + [ + [ + { + 'content-type': 'text/plain', + }, + { + 'Content-Type': null, + }, + ], + `NullableHeaders { 'content-type': null }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: 'name2=value2', + }, + ], + `NullableHeaders { 'cookie': 'name2=value2' }`, + ], + [ + [ + { + cookie: 'name1=value1', + Cookie: undefined, + }, + ], + `NullableHeaders { 'cookie': 'name1=value1' }`, + ], + [ + [ + { + cookie: ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, + ], + [ + [ + { + 'x-foo': ['name1=value1', 'name2=value2'], + }, + ], + `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, + ], + [ + [ + [ + ['cookie', 'name1=value1'], + ['cookie', 'name2=value2'], + ['Cookie', 'name3=value3'], + ], + ], + `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, + ], + [[undefined], `NullableHeaders { }`], + [[null], `NullableHeaders { }`], + ]; + for (const [input, expected] of cases) { + test(expected, () => { + expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); + }); + } +}); diff --git a/tests/cache.js b/tests/cache.js deleted file mode 100644 index f1f7b2c4..00000000 --- a/tests/cache.js +++ /dev/null @@ -1,186 +0,0 @@ -import chai from "chai"; -import sinon from "sinon"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams - -import ImageKit from "../index"; -import nock from "nock"; -var imagekit = new ImageKit(initializationParams); - -const dummyAPISuccessResponse = { - dummyKey: "dummyValue" -}; - -const dummyAPIErrorResponse = { - help: "help", - message: "message" -} - -describe("Cache purge API", function () { - describe("Request body check", function () { - it('Purge cache', function (done) { - var url = "http://ik.imagekit.io/demo/default-image.jpg"; - - const scope = nock('https://api.imagekit.io') - .post("/v1/files/purge") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.deep.equal({ - url: url - }); - done(); - return [200]; - }) - - imagekit.purgeCache(url); - }); - - it('Purge cache no url', function (done) { - imagekit.purgeCache("", function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing URL parameter for this request" - }); - done(); - }); - }); - - it('Purge cache', function (done) { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - done(); - return [200]; - }) - - imagekit.getPurgeCacheStatus(requestId); - }); - - it('Purge cache missing requestId', function (done) { - imagekit.getPurgeCacheStatus("", function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing Request ID parameter for this request" - }); - done(); - }); - }); - }); - - describe("Success callbacks", function () { - it('Purge cache', function (done) { - var url = "http://ik.imagekit.io/demo/default-image.jpg"; - - const scope = nock('https://api.imagekit.io') - .post("/v1/files/purge") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - imagekit.purgeCache(url, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Purge cache', function (done) { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.getPurgeCacheStatus(requestId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Purge cache promise', async function () { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - var response = await imagekit.getPurgeCacheStatus(requestId); - expect(response).to.be.deep.equal(dummyAPISuccessResponse); - return Promise.resolve(); - }); - }); - - describe("Error callbacks", function () { - it('Purge cache', function (done) { - var url = "http://ik.imagekit.io/demo/default-image.jpg"; - - const scope = nock('https://api.imagekit.io') - .post("/v1/files/purge") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.purgeCache(url, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Purge cache', function (done) { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.getPurgeCacheStatus(requestId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Purge cache promise', async function () { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - try { - await imagekit.getPurgeCacheStatus(requestId); - } catch(ex) { - expect(ex).to.be.deep.equal(dummyAPIErrorResponse); - return Promise.resolve(); - } - - return Promise.reject(); - }); - }); -}); - diff --git a/tests/custom-metadata-field.js b/tests/custom-metadata-field.js deleted file mode 100644 index 18ad8cac..00000000 --- a/tests/custom-metadata-field.js +++ /dev/null @@ -1,392 +0,0 @@ -import chai from "chai"; -import sinon from "sinon"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams - -import ImageKit from "../index"; -import nock from "nock"; -var imagekit = new ImageKit(initializationParams); - -const dummyAPISuccessResponse = { - id: "id", - name: "name", - label: "label", - schema: { - type: "number", - minValue: 10 - } -}; - -const dummyAPIErrorResponse = { - help: "help", - message: "message" -} - -describe("Custom metadata field API", function () { - describe("Request body check", function () { - it('Create field', function (done) { - const scope = nock('https://api.imagekit.io') - .post("/v1/customMetadataFields") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.deep.equal({ - name: "name", - label: "label", - schema: { - type: "number", - minValue: 10 - } - }); - done(); - return [200]; - }) - - imagekit.createCustomMetadataField({ - name: "name", - label: "label", - schema: { - type: "number", - minValue: 10 - } - }); - }); - - it('Create field missing name', function (done) { - imagekit.createCustomMetadataField({ - label: "label", - schema: { - type: "number", - minValue: 10 - } - }, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Missing name parameter for this request" - }); - done(); - }); - }); - - it('Create field missing label', function (done) { - imagekit.createCustomMetadataField({ - name: "name", - schema: { - type: "number", - minValue: 10 - } - }, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Missing label parameter for this request" - }); - done(); - }); - }); - - it('Create field missing schema', function (done) { - imagekit.createCustomMetadataField({ - name: "name", - label: "label" - }, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Missing schema parameter for this request" - }); - done(); - }); - }); - - it('Create field missing schema.type', function (done) { - imagekit.createCustomMetadataField({ - name: "name", - label: "label", - schema: { - minValue: 10 - } - }, (err, response) => { - expect(err).to.deep.equal({ - help: "schema should have a mandatory type field.", - message: "Invalid value for schema" - }); - done(); - }); - }); - - it('Get field', function (done) { - const scope = nock('https://api.imagekit.io') - .get("/v1/customMetadataFields") - .query({ - includeDeleted: false - }) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.be.empty; - done(); - return [200]; - }) - - imagekit.getCustomMetadataFields(); - }); - - it('Get field - includeDeleted true', function (done) { - const scope = nock('https://api.imagekit.io') - .get("/v1/customMetadataFields") - .query({ - includeDeleted: true - }) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.be.empty; - done(); - return [200]; - }) - - imagekit.getCustomMetadataFields({ - includeDeleted: true - }); - }); - - it('Update field', function (done) { - const scope = nock('https://api.imagekit.io') - .patch("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.deep.equal({ - schema: { - minValue: 10 - } - }); - done(); - return [200]; - }) - - imagekit.updateCustomMetadataField("fieldId", { - schema: { - minValue: 10 - } - }); - }); - - it('Update field only label', function (done) { - const scope = nock('https://api.imagekit.io') - .patch("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.deep.equal({ - label: "new-label" - }); - done(); - return [200]; - }) - - imagekit.updateCustomMetadataField("fieldId", { - label: "new-label" - }); - }); - - it('Update field missing fieldId', function (done) { - imagekit.updateCustomMetadataField(null, {}, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Missing fieldId parameter for this request" - }); - done(); - }); - }); - - it('Update field missing label and schema', function (done) { - imagekit.updateCustomMetadataField("fieldId", {}, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Both label and schema is missing" - }); - done(); - }); - }); - - it('Delete field', function (done) { - const scope = nock('https://api.imagekit.io') - .delete("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(requestBody).to.be.empty; - done(); - return [204]; - }) - - imagekit.deleteCustomMetadataField("fieldId"); - }); - - it('Delete field missing fieldId', function (done) { - imagekit.deleteCustomMetadataField(null, (err, response) => { - expect(err).to.deep.equal({ - help: "", - message: "Missing fieldId parameter for this request" - }); - done(); - }); - }); - - }); - - describe("Success callbacks", function () { - it('Create field', function (done) { - const scope = nock('https://api.imagekit.io') - .post("/v1/customMetadataFields") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - imagekit.createCustomMetadataField({ - name: "name", - label: "label", - schema: { - type: "number", - minValue: 10 - } - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Get fields', function (done) { - const scope = nock('https://api.imagekit.io') - .get("/v1/customMetadataFields") - .query({ - includeDeleted: false - }) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, [dummyAPISuccessResponse, dummyAPISuccessResponse]) - - var callback = sinon.spy(); - imagekit.getCustomMetadataFields({}, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, [dummyAPISuccessResponse, dummyAPISuccessResponse]); - done(); - }, 50); - }); - - it('Update field', function (done) { - const scope = nock('https://api.imagekit.io') - .patch("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - imagekit.updateCustomMetadataField("fieldId", { - schema: { - minValue: 20 - } - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Delete field', function (done) { - const scope = nock('https://api.imagekit.io') - .delete("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, null) - - var callback = sinon.spy(); - imagekit.deleteCustomMetadataField("fieldId", callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, {}); - done(); - }, 50); - }); - }); - - describe("Error callbacks", function () { - it('Create field', function (done) { - const scope = nock('https://api.imagekit.io') - .post("/v1/customMetadataFields") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.createCustomMetadataField({ - name: "name", - label: "label", - schema: { - type: "number", - minValue: 10 - } - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Get fields', function (done) { - const scope = nock('https://api.imagekit.io') - .get("/v1/customMetadataFields") - .query({ - includeDeleted: false - }) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.getCustomMetadataFields({}, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Update field', function (done) { - const scope = nock('https://api.imagekit.io') - .patch("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.updateCustomMetadataField("fieldId", { - schema: { - minValue: 20 - } - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Delete field', function (done) { - const scope = nock('https://api.imagekit.io') - .delete("/v1/customMetadataFields/fieldId") - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.deleteCustomMetadataField("fieldId", callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - }); -}); - diff --git a/tests/data/index.js b/tests/data/index.js deleted file mode 100644 index 6d7d2f51..00000000 --- a/tests/data/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports.initializationParams = { - publicKey: "test_public_key", - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - privateKey: "test_private_key", - authenticationEndpoint: "http://test/auth" -} \ No newline at end of file diff --git a/tests/data/test_image.jpg b/tests/data/test_image.jpg deleted file mode 100644 index 8102e278be0fe0aabae92a9ea9f3a3a62c43568d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199185 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 diff --git a/tests/e2e/node-js/index.js b/tests/e2e/node-js/index.js deleted file mode 100644 index 0e411d94..00000000 --- a/tests/e2e/node-js/index.js +++ /dev/null @@ -1,31 +0,0 @@ -const ImageKit = require("imagekit"); - -try { - var imagekit = new ImageKit({ - publicKey: "public", - privateKey: "private", - urlEndpoint: "https://ik.imagekit.io/xyz" - }); - - var url = imagekit.url({ - path: "Screenshot_2022-07-05_at_1.06.47_PM_2558irHgF.png", - transformation: [{ - width: 100, - raw: "sdfdsf" - }], - transformationPosition: "path" - }) - - if (url === "https://ik.imagekit.io/xyz/tr:w-100,sdfdsf/Screenshot_2022-07-05_at_1.06.47_PM_2558irHgF.png") { - process.exit(0); - } else { - console.log("Invalid URL", url); - process.exit(1); - } - - -} catch (ex) { - console.log(ex) - process.exit(1); -} - diff --git a/tests/e2e/typescript/index.ts b/tests/e2e/typescript/index.ts deleted file mode 100644 index e46bf8b1..00000000 --- a/tests/e2e/typescript/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import ImageKit from "imagekit"; - -try { - var imagekit = new ImageKit({ - publicKey: "public", - privateKey: "private", - urlEndpoint: "https://ik.imagekit.io/xyz" - }); - - var url = imagekit.url({ - path: "Screenshot_2022-07-05_at_1.06.47_PM_2558irHgF.png", - transformation: [{ - width: 100, - raw: "sdfdsf" - }], - transformationPosition: "path" - }) - - if (url === "https://ik.imagekit.io/xyz/tr:w-100,sdfdsf/Screenshot_2022-07-05_at_1.06.47_PM_2558irHgF.png") { - process.exit(0); - } else { - console.log("Invalid URL", url); - process.exit(1); - } - - -} catch (ex) { - console.log(ex) - process.exit(1); -} \ No newline at end of file diff --git a/tests/e2e/typescript/tsconfig.json b/tests/e2e/typescript/tsconfig.json deleted file mode 100644 index 75dcaeac..00000000 --- a/tests/e2e/typescript/tsconfig.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} diff --git a/tests/form.test.ts b/tests/form.test.ts new file mode 100644 index 00000000..c978df14 --- /dev/null +++ b/tests/form.test.ts @@ -0,0 +1,85 @@ +import { multipartFormRequestOptions, createForm } from '@imagekit/nodejs/internal/uploads'; +import { toFile } from '@imagekit/nodejs/core/uploads'; + +describe('form data validation', () => { + test('valid values do not error', async () => { + await multipartFormRequestOptions( + { + body: { + foo: 'foo', + string: 1, + bool: true, + file: await toFile(Buffer.from('some-content')), + blob: new Blob(['Some content'], { type: 'text/plain' }), + }, + }, + fetch, + ); + }); + + test('null', async () => { + await expect(() => + multipartFormRequestOptions( + { + body: { + null: null, + }, + }, + fetch, + ), + ).rejects.toThrow(TypeError); + }); + + test('undefined is stripped', async () => { + const form = await createForm( + { + foo: undefined, + bar: 'baz', + }, + fetch, + ); + expect(form.has('foo')).toBe(false); + expect(form.get('bar')).toBe('baz'); + }); + + test('nested undefined property is stripped', async () => { + const form = await createForm( + { + bar: { + baz: undefined, + }, + }, + fetch, + ); + expect(Array.from(form.entries())).toEqual([]); + + const form2 = await createForm( + { + bar: { + foo: 'string', + baz: undefined, + }, + }, + fetch, + ); + expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); + }); + + test('nested undefined array item is stripped', async () => { + const form = await createForm( + { + bar: [undefined, undefined], + }, + fetch, + ); + expect(Array.from(form.entries())).toEqual([]); + + const form2 = await createForm( + { + bar: [undefined, 'foo'], + }, + fetch, + ); + expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); + }); +}); diff --git a/tests/helpers/errors.js b/tests/helpers/errors.js deleted file mode 100644 index 464df30c..00000000 --- a/tests/helpers/errors.js +++ /dev/null @@ -1,2 +0,0 @@ -import errors from '../../libs/constants/errorMessages'; -module.exports = { ...errors }; diff --git a/tests/helpers/spies.js b/tests/helpers/spies.js deleted file mode 100644 index 1dbbc24b..00000000 --- a/tests/helpers/spies.js +++ /dev/null @@ -1,11 +0,0 @@ -// packages -import sinon from 'sinon'; -// internal modules -import pHashUtils from "../../utils/phash"; - -// spies -const pHashDistanceSpy = sinon.spy(pHashUtils, 'pHashDistance'); - -module.exports = { - pHashDistanceSpy, -}; diff --git a/tests/index.test.ts b/tests/index.test.ts new file mode 100644 index 00000000..37af5cd3 --- /dev/null +++ b/tests/index.test.ts @@ -0,0 +1,826 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIPromise } from '@imagekit/nodejs/core/api-promise'; + +import util from 'node:util'; +import ImageKit from '@imagekit/nodejs'; +import { APIUserAbortError } from '@imagekit/nodejs'; +const defaultFetch = fetch; + +describe('instantiate client', () => { + const env = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...env }; + }); + + afterEach(() => { + process.env = env; + }); + + describe('defaultHeaders', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + defaultHeaders: { 'X-My-Default-Header': '2' }, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + test('they are used in the request', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); + expect(req.headers.get('x-my-default-header')).toEqual('2'); + }); + + test('can ignore `undefined` and leave the default', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + headers: { 'X-My-Default-Header': undefined }, + }); + expect(req.headers.get('x-my-default-header')).toEqual('2'); + }); + + test('can be removed with `null`', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + headers: { 'X-My-Default-Header': null }, + }); + expect(req.headers.has('x-my-default-header')).toBe(false); + }); + }); + describe('logging', () => { + const env = process.env; + + beforeEach(() => { + process.env = { ...env }; + process.env['IMAGE_KIT_LOG'] = undefined; + }); + + afterEach(() => { + process.env = env; + }); + + const forceAPIResponseForClient = async (client: ImageKit) => { + await new APIPromise( + client, + Promise.resolve({ + response: new Response(), + controller: new AbortController(), + requestLogID: 'log_000000', + retryOfRequestLogID: undefined, + startTime: Date.now(), + options: { + method: 'get', + path: '/', + }, + }), + ); + }; + + test('debug logs when log level is debug', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new ImageKit({ + logger: logger, + logLevel: 'debug', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('default logLevel is warn', async () => { + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.logLevel).toBe('warn'); + }); + + test('debug logs are skipped when log level is info', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const client = new ImageKit({ + logger: logger, + logLevel: 'info', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('debug logs happen with debug env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['IMAGE_KIT_LOG'] = 'debug'; + const client = new ImageKit({ + logger: logger, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.logLevel).toBe('debug'); + + await forceAPIResponseForClient(client); + expect(debugMock).toHaveBeenCalled(); + }); + + test('warn when env var level is invalid', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['IMAGE_KIT_LOG'] = 'not a log level'; + const client = new ImageKit({ + logger: logger, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.logLevel).toBe('warn'); + expect(warnMock).toHaveBeenCalledWith( + 'process.env[\'IMAGE_KIT_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', + ); + }); + + test('client log level overrides env var', async () => { + const debugMock = jest.fn(); + const logger = { + debug: debugMock, + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + process.env['IMAGE_KIT_LOG'] = 'debug'; + const client = new ImageKit({ + logger: logger, + logLevel: 'off', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + await forceAPIResponseForClient(client); + expect(debugMock).not.toHaveBeenCalled(); + }); + + test('no warning logged for invalid env var level + valid client level', async () => { + const warnMock = jest.fn(); + const logger = { + debug: jest.fn(), + info: jest.fn(), + warn: warnMock, + error: jest.fn(), + }; + + process.env['IMAGE_KIT_LOG'] = 'not a log level'; + const client = new ImageKit({ + logger: logger, + logLevel: 'debug', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.logLevel).toBe('debug'); + expect(warnMock).not.toHaveBeenCalled(); + }); + }); + + describe('defaultQuery', () => { + test('with null query params given', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + defaultQuery: { apiVersion: 'foo' }, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); + }); + + test('multiple default query params', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + defaultQuery: { apiVersion: 'foo', hello: 'world' }, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); + }); + + test('overriding with `undefined`', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + defaultQuery: { hello: 'world' }, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); + }); + }); + + test('custom fetch', async () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: (url) => { + return Promise.resolve( + new Response(JSON.stringify({ url, custom: true }), { + headers: { 'Content-Type': 'application/json' }, + }), + ); + }, + }); + + const response = await client.get('/foo'); + expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); + }); + + test('explicit global fetch', async () => { + // make sure the global fetch type is assignable to our Fetch type + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: defaultFetch, + }); + }); + + test('custom signal', async () => { + const client = new ImageKit({ + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: (...args) => { + return new Promise((resolve, reject) => + setTimeout( + () => + defaultFetch(...args) + .then(resolve) + .catch(reject), + 300, + ), + ); + }, + }); + + const controller = new AbortController(); + setTimeout(() => controller.abort(), 200); + + const spy = jest.spyOn(client, 'request'); + + await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); + expect(spy).toHaveBeenCalledTimes(1); + }); + + test('normalized method', async () => { + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + capturedRequest = init; + return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + }); + + await client.patch('/foo'); + expect(capturedRequest?.method).toEqual('PATCH'); + }); + + describe('baseUrl', () => { + test('trailing slash', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/custom/path/', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); + }); + + test('no trailing slash', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/custom/path', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); + }); + + afterEach(() => { + process.env['IMAGE_KIT_BASE_URL'] = undefined; + }); + + test('explicit option', () => { + const client = new ImageKit({ + baseURL: 'https://example.com', + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.baseURL).toEqual('https://example.com'); + }); + + test('env variable', () => { + process.env['IMAGE_KIT_BASE_URL'] = 'https://example.com/from_env'; + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.baseURL).toEqual('https://example.com/from_env'); + }); + + test('empty env variable', () => { + process.env['IMAGE_KIT_BASE_URL'] = ''; // empty + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.baseURL).toEqual('https://api.imagekit.io'); + }); + + test('blank env variable', () => { + process.env['IMAGE_KIT_BASE_URL'] = ' '; // blank + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.baseURL).toEqual('https://api.imagekit.io'); + }); + + test('in request options', () => { + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/option/foo', + ); + }); + + test('in request options overridden by client options', () => { + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: 'http://localhost:5000/client', + }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/client/foo', + ); + }); + + test('in request options overridden by env variable', () => { + process.env['IMAGE_KIT_BASE_URL'] = 'http://localhost:5000/env'; + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/env/foo', + ); + }); + }); + + test('maxRetries option is correctly set', () => { + const client = new ImageKit({ + maxRetries: 4, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + expect(client.maxRetries).toEqual(4); + + // default + const client2 = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client2.maxRetries).toEqual(2); + }); + + describe('withOptions', () => { + test('creates a new client with overridden options', async () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + maxRetries: 3, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + const newClient = client.withOptions({ + maxRetries: 5, + baseURL: 'http://localhost:5001/', + }); + + // Verify the new client has updated options + expect(newClient.maxRetries).toEqual(5); + expect(newClient.baseURL).toEqual('http://localhost:5001/'); + + // Verify the original client is unchanged + expect(client.maxRetries).toEqual(3); + expect(client.baseURL).toEqual('http://localhost:5000/'); + + // Verify it's a different instance + expect(newClient).not.toBe(client); + expect(newClient.constructor).toBe(client.constructor); + }); + + test('inherits options from the parent client', async () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + defaultHeaders: { 'X-Test-Header': 'test-value' }, + defaultQuery: { 'test-param': 'test-value' }, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + const newClient = client.withOptions({ + baseURL: 'http://localhost:5001/', + }); + + // Test inherited options remain the same + expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); + + const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); + expect(req.headers.get('x-test-header')).toEqual('test-value'); + }); + + test('respects runtime property changes when creating new client', () => { + const client = new ImageKit({ + baseURL: 'http://localhost:5000/', + timeout: 1000, + privateAPIKey: 'My Private API Key', + password: 'My Password', + }); + + // Modify the client properties directly after creation + client.baseURL = 'http://localhost:6000/'; + client.timeout = 2000; + + // Create a new client with withOptions + const newClient = client.withOptions({ + maxRetries: 10, + }); + + // Verify the new client uses the updated properties, not the original ones + expect(newClient.baseURL).toEqual('http://localhost:6000/'); + expect(newClient.timeout).toEqual(2000); + expect(newClient.maxRetries).toEqual(10); + + // Original client should still have its modified properties + expect(client.baseURL).toEqual('http://localhost:6000/'); + expect(client.timeout).toEqual(2000); + expect(client.maxRetries).not.toEqual(10); + + // Verify URL building uses the updated baseURL + expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar'); + }); + }); + + test('with environment variable arguments', () => { + // set options via env var + process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'My Private API Key'; + process.env['ORG_MY_PASSWORD_TOKEN'] = 'My Password'; + const client = new ImageKit(); + expect(client.privateAPIKey).toBe('My Private API Key'); + expect(client.password).toBe('My Password'); + }); + + test('with overridden environment variable arguments', () => { + // set options via env var + process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'another My Private API Key'; + process.env['ORG_MY_PASSWORD_TOKEN'] = 'another My Password'; + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + expect(client.privateAPIKey).toBe('My Private API Key'); + expect(client.password).toBe('My Password'); + }); +}); + +describe('request building', () => { + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + + describe('custom headers', () => { + test('handles undefined', async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: { value: 'hello' }, + headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, + }); + expect(req.headers.get('x-foo')).toEqual('bar'); + expect(req.headers.get('x-Foo')).toEqual('bar'); + expect(req.headers.get('X-Foo')).toEqual('bar'); + expect(req.headers.get('x-baz')).toEqual(null); + }); + }); +}); + +describe('default encoder', () => { + const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + + class Serializable { + toJSON() { + return { $type: 'Serializable' }; + } + } + class Collection { + #things: T[]; + constructor(things: T[]) { + this.#things = Array.from(things); + } + toJSON() { + return Array.from(this.#things); + } + [Symbol.iterator]() { + return this.#things[Symbol.iterator]; + } + } + for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { + test(`serializes ${util.inspect(jsonValue)} as json`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: jsonValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('application/json'); + expect(req.body).toBe(JSON.stringify(jsonValue)); + }); + } + + const encoder = new TextEncoder(); + const asyncIterable = (async function* () { + yield encoder.encode('a\n'); + yield encoder.encode('b\n'); + yield encoder.encode('c\n'); + })(); + for (const streamValue of [ + [encoder.encode('a\nb\nc\n')][Symbol.iterator](), + new Response('a\nb\nc\n').body, + asyncIterable, + ]) { + test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: streamValue, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual(null); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); + } + + test(`can set content-type for ReadableStream`, async () => { + const { req } = await client.buildRequest({ + path: '/foo', + method: 'post', + body: new Response('a\nb\nc\n').body, + headers: { 'Content-Type': 'text/plain' }, + }); + expect(req.headers).toBeInstanceOf(Headers); + expect(req.headers.get('content-type')).toEqual('text/plain'); + expect(req.body).toBeInstanceOf(ReadableStream); + expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); + }); +}); + +describe('retries', () => { + test('retry on timeout', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Promise( + (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), + ); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + timeout: 10, + fetch: testFetch, + }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); + + test('retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + maxRetries: 4, + }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); + expect(count).toEqual(3); + }); + + test('omit retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + maxRetries: 4, + }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + headers: { 'X-Stainless-Retry-Count': null }, + }), + ).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); + }); + + test('omit retry count header by default', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + maxRetries: 4, + defaultHeaders: { 'X-Stainless-Retry-Count': null }, + }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + }), + ).toEqual({ a: 1 }); + + expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); + }); + + test('overwrite retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + maxRetries: 4, + }); + + expect( + await client.request({ + path: '/foo', + method: 'get', + headers: { 'X-Stainless-Retry-Count': '42' }, + }), + ).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); + }); + + test('retry on 429 with retry-after', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); + + test('retry on 429 with retry-after-ms', async () => { + let count = 0; + const testFetch = async ( + url: string | URL | Request, + { signal }: RequestInit = {}, + ): Promise => { + if (count++ === 0) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After-Ms': '10', + }, + }); + } + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + fetch: testFetch, + }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + expect(count).toEqual(2); + expect( + await client + .request({ path: '/foo', method: 'get' }) + .asResponse() + .then((r) => r.text()), + ).toEqual(JSON.stringify({ a: 1 })); + expect(count).toEqual(3); + }); +}); diff --git a/tests/initialization.js b/tests/initialization.js deleted file mode 100644 index 883753e1..00000000 --- a/tests/initialization.js +++ /dev/null @@ -1,65 +0,0 @@ -import chai from "chai"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams -import ImageKit from "../index"; - -describe("Initialization checks", function () { - var imagekit = new ImageKit(initializationParams); - - it('should throw error', function () { - try { - new ImageKit({}); - } catch(err) { - expect(err.message).to.be.equal('Missing publicKey during ImageKit initialization'); - } - }); - - it('should throw error', function () { - try { - new ImageKit({ - publicKey: "test_public_key" - }); - } catch(err) { - expect(err.message).to.be.equal('Missing privateKey during ImageKit initialization'); - } - }); - - it('should throw error', function () { - try { - new ImageKit({ - publicKey: "test_public_key", - privateKey: "test_private_key" - }); - } catch(err) { - expect(err.message).to.be.equal('Missing urlEndpoint during ImageKit initialization'); - } - }); - - it('callback', function () { - var imagekit = new ImageKit({ - urlEndpoint: "https://ik.imagekit.io/demo", - publicKey: "test_public_key", - privateKey: "test_private_key" - }); - try { - imagekit.getFileDetails("fileId","wrongCallback"); - } catch(err) { - expect(err.message).to.be.equal("Callback must be a function.") - } - }); - - it('should have options object', function () { - expect(imagekit.options).to.be.an('object'); - }); - - it('should have correctly initialized options object.', function () { - expect(imagekit.options).to.have.property('publicKey').to.be.equal(initializationParams.publicKey); - expect(imagekit.options).to.have.property('urlEndpoint').to.be.equal(initializationParams.urlEndpoint); - expect(imagekit.options).to.have.property('authenticationEndpoint').to.be.equal(initializationParams.authenticationEndpoint); - }); - - it("should have callable functions 'url' and 'upload'", function () { - expect(imagekit.url).to.exist.and.to.be.a('function'); - expect(imagekit.upload).to.exist.and.to.be.a('function'); - }); -}); \ No newline at end of file diff --git a/tests/mediaLibrary.js b/tests/mediaLibrary.js deleted file mode 100644 index 6f9defa7..00000000 --- a/tests/mediaLibrary.js +++ /dev/null @@ -1,1705 +0,0 @@ -import chai from "chai"; -import sinon from "sinon"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams -import ImageKit from "../index"; -import nock from "nock"; -var imagekit = new ImageKit(initializationParams); - -const dummyAPISuccessResponse = { - dummyKey: "dummyValue" -}; - -const dummyAPIErrorResponse = { - help: "help", - message: "message" -} - -describe("Media library APIs", function () { - describe("Request body check", function () { - it('Delete single file', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/files/${fileId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}`) - done(); - return [200] - }) - - imagekit.deleteFile(fileId); - }); - - it('Delete single file missing fileId', function (done) { - imagekit.deleteFile(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('Delete file versions', function (done) { - var fileId = "23902390239203923"; - var versionId = "versionId" - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/files/${fileId}/versions/${versionId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/versions/${versionId}`) - done(); - return [200] - }) - - imagekit.deleteFileVersion({ - fileId, - versionId - }); - }); - - it('Delete file versions missing fileId', function (done) { - imagekit.deleteFileVersion(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('Delete file versions missing versionId', function (done) { - imagekit.deleteFileVersion({ - fileId: "fileId" - }, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing versionId parameter for this request" - }) - done(); - }); - }); - - it('Bulk add tags missing tags', function (done) { - var fileIds = ["23902390239203923"] - imagekit.bulkAddTags(fileIds, null, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for tags", - help: "tags should be a non empty array of string like ['tag1', 'tag2']." - }) - done(); - }); - }); - - it('Bulk add tags missing fileId', function (done) { - var tags = ['tag1']; - imagekit.bulkAddTags(null, tags, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for fileIds", - help: "fileIds should be an array of fileId of the files. The array should have atleast one fileId." - }) - done(); - }); - }); - - it('Bulk remove tags', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/removeTags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/removeTags`) - expect(requestBody).to.be.deep.equal({ - fileIds: [fileId, fileId], - tags: ["tag1", "tag2"] - }) - done(); - return [200] - }) - - imagekit.bulkRemoveTags([fileId, fileId], ["tag1", "tag2"]); - }); - - it('Bulk remove tags missing fileId', function (done) { - var tags = ['tag1']; - imagekit.bulkRemoveTags(null, tags, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for fileIds", - help: "fileIds should be an array of fileId of the files. The array should have atleast one fileId." - }) - done(); - }); - }); - - it('Bulk remove tags missing tags', function (done) { - var fileIds = ["23902390239203923"] - imagekit.bulkRemoveTags(fileIds, null, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for tags", - help: "tags should be a non empty array of string like ['tag1', 'tag2']." - }) - done(); - }); - }); - - it('Bulk remove AITags', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/removeAITags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/removeAITags`) - expect(requestBody).to.be.deep.equal({ - fileIds: [fileId, fileId], - AITags: ["tag1", "tag2"] - }) - done(); - return [200] - }) - - imagekit.bulkRemoveAITags([fileId, fileId], ["tag1", "tag2"]); - }); - - it('Bulk remove AITags missing fileId', function (done) { - var tags = ['tag1']; - imagekit.bulkRemoveAITags(null, tags, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for fileIds", - help: "fileIds should be an array of fileId of the files. The array should have atleast one fileId." - }) - done(); - }); - }); - - it('Bulk remove AITags missing tags', function (done) { - var fileIds = ["23902390239203923"] - imagekit.bulkRemoveAITags(fileIds, null, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for tags", - help: "tags should be a non empty array of string like ['tag1', 'tag2']." - }) - done(); - }); - }); - - it('Copy file - default options', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/copy`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/copy`) - expect(requestBody).to.be.deep.equal({ - sourceFilePath: "/xyz", - destinationPath: "/abc", - includeFileVersions: false - }) - done(); - return [200] - }) - - imagekit.copyFile({ - sourceFilePath: "/xyz", - destinationPath: "/abc" - }); - }); - - it('Copy file', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/copy`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/copy`) - expect(requestBody).to.be.deep.equal({ - sourceFilePath: "/xyz.jpg", - destinationPath: "/abc", - includeFileVersions: true - }) - done(); - return [200] - }) - - imagekit.copyFile({ - sourceFilePath: "/xyz.jpg", - destinationPath: "/abc", - includeFileVersions: true - }); - }); - - it('Copy file invalid folder path', function (done) { - var sourceFilePath = "/file.jpg"; - imagekit.copyFile({ sourceFilePath, destinationPath: null }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid destinationPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Copy file invalid file path', function (done) { - var destinationPath = "/"; - imagekit.copyFile({ sourceFilePath: null, destinationPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid sourceFilePath value", - help: "It should be a string like /path/to/file.jpg'" - }) - done(); - }); - }); - - it('Copy file invalid includeFileVersions value', function (done) { - var sourceFilePath = "/sdf.jpg"; - var destinationPath = "/"; - imagekit.copyFile({ sourceFilePath, destinationPath, includeFileVersions: "sdf" }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid includeFileVersions value", - help: "It should be a boolean" - }) - done(); - }); - }); - - it('Move file', function (done) { - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/move`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/move`) - expect(requestBody).to.be.deep.equal({ - sourceFilePath: "/abc.jpg", - destinationPath: "/xyz" - }) - done(); - return [200] - }); - - imagekit.moveFile({ sourceFilePath: "/abc.jpg", destinationPath: "/xyz" }); - }); - - it('Move file invalid folder path', function (done) { - var sourceFilePath = "/file.jpg"; - imagekit.moveFile({ sourceFilePath, destinationPath: null }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid destinationPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Move file invalid file path', function (done) { - var destinationPath = "/"; - imagekit.moveFile({ sourceFilePath: null, destinationPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid sourceFilePath value", - help: "It should be a string like /path/to/file.jpg'" - }) - done(); - }); - }); - - it('Rename file - default purgeCache value', function (done) { - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/rename`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/rename`) - expect(requestBody).to.be.deep.equal({ - filePath: "/abc.jpg", - newFileName: "test.jpg", - purgeCache: false - }) - done(); - return [200] - }); - - imagekit.renameFile({ - filePath: "/abc.jpg", - newFileName: "test.jpg" - }) - }); - - it('Rename file', function (done) { - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/rename`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/rename`) - expect(requestBody).to.be.deep.equal({ - filePath: "/abc.jpg", - newFileName: "test.jpg", - purgeCache: true - }) - done(); - return [200] - }); - - imagekit.renameFile({ - filePath: "/abc.jpg", - newFileName: "test.jpg", - purgeCache: true - }) - }); - - it('Rename file - invalid filePath', function (done) { - imagekit.renameFile({ - filePath: null, - newFileName: "test.jpg" - }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for filePath", - help: "Pass the full path of the file. For example - /path/to/file.jpg" - }) - done(); - }); - }); - - it('Rename file - invalid newFileName', function (done) { - imagekit.renameFile({ - filePath: "/xyz.jpg", - newFileName: null, - }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for newFileName. It should be a string.", - help: "" - }) - done(); - }); - }); - - it('Rename file - invalid purgeCache', function (done) { - imagekit.renameFile({ - filePath: "/xyz.jpg", - newFileName: "test.pdf", - purgeCache: "sdf" - }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for purgeCache. It should be boolean.", - help: "" - }) - done(); - }); - }); - - it('Restore file version', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/${fileId}/versions/${versionId}/restore`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/versions/${versionId}/restore`) - expect(requestBody).to.be.empty; - done(); - return [200] - }); - - imagekit.restoreFileVersion({ - fileId, - versionId, - }) - }); - - it('Restore file version - missing fileId', function (done) { - imagekit.restoreFileVersion({ - fileId: null, - versionId: "versionId", - }, function (err, response) { - expect(err).to.deep.equal({ - message: "Missing fileId parameter for this request", - help: "" - }) - done(); - }); - }); - - it('Restore file version - missing versionId', function (done) { - imagekit.restoreFileVersion({ - fileId: "fileId", - versionId: null - }, function (err, response) { - expect(err).to.deep.equal({ - message: "Missing versionId parameter for this request", - help: "" - }) - done(); - }); - }); - - it('Copy folder - default options', function (done) { - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/copyFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/bulkJobs/copyFolder`) - expect(requestBody).to.be.deep.equal({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination", - includeFileVersions: false - }) - done(); - return [200] - }); - - imagekit.copyFolder({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination" - }) - }); - - it('Copy folder', function (done) { - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/copyFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/bulkJobs/copyFolder`) - expect(requestBody).to.be.deep.equal({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination", - includeFileVersions: true - }) - done(); - return [200] - }); - - imagekit.copyFolder({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination", - includeFileVersions: true - }) - }); - - it('Copy folder invalid sourceFolderPath', function (done) { - var destinationPath = "/"; - imagekit.copyFolder({ sourceFolderPath: null, destinationPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid sourceFolderPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Copy folder invalid destinationPath', function (done) { - var sourceFolderPath = "/"; - imagekit.copyFolder({ sourceFolderPath, destinationPath: null }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid destinationPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Copy folder invalid includeFileVersions', function (done) { - var sourceFolderPath = "/"; - imagekit.copyFolder({ sourceFolderPath, destinationPath: "/sdf", includeFileVersions: "sdf" }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid includeFileVersions value", - help: "It should be a boolean" - }) - done(); - }); - }); - - it('Move folder', function (done) { - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/moveFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/bulkJobs/moveFolder`) - expect(requestBody).to.be.deep.equal({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination" - }) - done(); - return [200] - }); - - imagekit.moveFolder({ - sourceFolderPath: "/source-folder", - destinationPath: "/destination" - }) - }); - - it('Move folder invalid destinationPath', function (done) { - var sourceFolderPath = "/"; - imagekit.moveFolder({ sourceFolderPath, destinationPath: null }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid destinationPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Move folder invalid sourceFolderPath', function (done) { - var destinationPath = "/"; - imagekit.moveFolder({ sourceFolderPath: null, destinationPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid sourceFolderPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Create folder', function (done) { - const scope = nock('https://api.imagekit.io') - .post(`/v1/folder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/folder`) - expect(requestBody).to.be.deep.equal({ - folderName: "abc", - parentFolderPath: "/path/to/folder" - }) - done(); - return [200] - }); - - imagekit.createFolder({ - folderName: "abc", - parentFolderPath: "/path/to/folder" - }) - }); - - it('Create folder invalid name', function (done) { - var folderName = ""; - var parentFolderPath = ""; - imagekit.createFolder({ folderName, parentFolderPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid folderName value", - help: "" - }) - done(); - }); - }); - - it('Create folder invalid path', function (done) { - var folderName = "folder1"; - var parentFolderPath = ""; - imagekit.createFolder({ folderName, parentFolderPath }, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid parentFolderPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Delete folder', function (done) { - const scope = nock('https://api.imagekit.io') - .delete(`/v1/folder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/folder`) - expect(requestBody).to.be.deep.equal({ - folderPath: "/path/to/folder", - }) - done(); - return [200] - }); - - imagekit.deleteFolder("/path/to/folder") - }); - - it('Delete folder invalid path', function (done) { - imagekit.deleteFolder(null, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid folderPath value", - help: "It should be a string like '/path/to/folder'" - }) - done(); - }); - }); - - it('Get file metadata using fileId', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/metadata`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/metadata`) - done() - return [200] - }) - - imagekit.getFileMetadata(fileId); - }); - - it('Get file metadata using fileId missing fileId', function (done) { - imagekit.getFileMetadata(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Pass either fileId or remote URL of the image as first parameter" - }) - done(); - }); - }); - - it('Get file details', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/details`) - done() - return [200] - }) - - imagekit.getFileDetails(fileId); - }); - - it('Get file details missing fileId', function (done) { - imagekit.getFileDetails(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('Get all file versions', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/versions`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/versions`) - done() - return [200] - }) - - imagekit.getFileVersions(fileId); - }); - - it('Get all file versions - missing fileId', function (done) { - imagekit.getFileVersions(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('Get file versions details', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/versions/${versionId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/versions/${versionId}`) - done() - return [200] - }) - - imagekit.getFileVersionDetails({ - fileId, - versionId - }); - }); - - it('Get file versions details - missing fileId', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - - imagekit.getFileVersionDetails({ - fileId: null, - versionId - }, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('Get file versions details - missing versionId', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - - imagekit.getFileVersionDetails({ - fileId, - versionId: null - }, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing versionId parameter for this request" - }) - done(); - }); - }); - - it('Update file details', function (done) { - var fileId = "23902390239203923"; - - var updateData = { - tags: ["tag1", "tag2"], - customCoordinates: "10,10,100,100", - extensions: [ - { - name: "google-auto-tagging", - maxTags: 5, - minConfidence: 95 - } - ], - customMetadata: { - SKU: 10 - }, - webhookUrl: "https://some-domain/some-api-id" - } - - const scope = nock('https://api.imagekit.io') - .patch(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/details`); - expect(requestBody).to.deep.equal(updateData); - done() - }) - - imagekit.updateFileDetails(fileId, updateData); - }); - - - it('Update publish status', function (done) { - var fileId = "23902390239203923"; - - var updateData = { - publish: { - isPublished: false, - }, - }; - - const scope = nock('https://api.imagekit.io') - .patch(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.path).equal(`/v1/files/${fileId}/details`); - expect(requestBody).to.deep.equal(updateData); - done() - }) - - imagekit.updateFileDetails(fileId, updateData); - }); - - it('Update file details invalid updateData', function (done) { - var fileId = "23902390239203923"; - - imagekit.updateFileDetails(fileId, null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing file update data for this request" - }) - done(); - }); - }); - - it('Update file details missing fileId', function (done) { - var updateData = { - tags: "sdf", - customCoordinates: "10,10,100,100" - } - - imagekit.updateFileDetails(null, updateData, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing fileId parameter for this request" - }) - done(); - }); - }); - - it('List files', function (done) { - var listOptions = { - skip: 0, - limit: 100, - tags: ["t-shirt", "summer"] - } - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .query({ - skip: listOptions.skip, - limit: listOptions.limit, - tags: listOptions.tags.join(",") - }) - .reply(function (uri, requestBody) { - expect(requestBody).equal("") - done() - return [200] - }) - - imagekit.listFiles(listOptions); - }); - - it('List files empty list options', function (done) { - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .query(actualQueryParams => { - if (Object.keys(actualQueryParams).length) { - done("query params should have been empty") - } else { - done(); - } - return true; - }) - .reply(function () { - return [200, dummyAPISuccessResponse] - }) - - imagekit.listFiles(); - }); - - it('List files empty invalid options', function (done) { - imagekit.listFiles("invalid", function (err, response) { - expect(err).to.deep.equal({ - message: "Pass a valid JSON list options e.g. {skip: 10, limit: 100}.", - help: "" - }) - done(); - }); - }); - - it('Bulk file delete by fileids', function (done) { - var fileIds = ["fileId1", "fileId2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/batch/deleteByFileIds`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(requestBody).to.deep.equal({ - fileIds: fileIds - }) - done() - }) - - imagekit.bulkDeleteFiles(fileIds); - }); - - it('Bulk file delete by fileids missing fileIds', function (done) { - imagekit.bulkDeleteFiles(null, function (err, response) { - expect(err).to.deep.equal({ - message: "Invalid value for fileIds", - help: "fileIds should be an array of fileId of the files. The array should have atleast one fileId." - }) - done(); - }); - }); - - it('Get bulk job status', function (done) { - var jobId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/bulkJobs/${jobId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(function (uri, requestBody) { - expect(this.req.path).equal(`/v1/bulkJobs/${jobId}`) - done(); - return [200] - }) - - imagekit.getBulkJobStatus(jobId); - }); - - it('Get bulk job status missing jobId', function (done) { - imagekit.getBulkJobStatus(null, function (err, response) { - expect(err).to.deep.equal({ - help: "", - message: "Missing jobId parameter" - }) - done(); - }); - }); - }); - - describe("Success callbacks", function () { - it('Delete single file', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/files/${fileId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, null) - - var callback = sinon.spy(); - - imagekit.deleteFile(fileId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, {}); - done(); - }, 50); - }); - - it('Get file metadata using fileId', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/metadata`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.getFileMetadata(fileId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Get file metadata using remote URL', function (done) { - var url = "https://ik.imagekit.io/demo/image.jpg"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/metadata`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .query({ - url: url - }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.getFileMetadata(url, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Get file details', function (done) { - var fileId = "23902390239203923"; - - var callback = sinon.spy(); - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - imagekit.getFileDetails(fileId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Update file details', function (done) { - var fileId = "23902390239203923"; - - var updateData = { - tags: ["tag1", "tag2"], - customCoordinates: "10,10,100,100" - } - - var callback = sinon.spy(); - - const scope = nock('https://api.imagekit.io') - .patch(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - imagekit.updateFileDetails(fileId, updateData, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('List files', function (done) { - var listOptions = { - skip: 0, - limit: 100 - } - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .query(listOptions) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.listFiles(listOptions, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Bulk file delete by fileids', function (done) { - var fileIds = ["fileId1", "fileId2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/batch/deleteByFileIds`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - - var callback = sinon.spy(); - - imagekit.bulkDeleteFiles(fileIds, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Bulk add tags', function (done) { - var fileIds = ["fileId1", "fileId2"]; - var tags = ["tag1", "tag2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/addTags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.bulkAddTags(fileIds, tags, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Bulk remove tags', function (done) { - var fileIds = ["fileId1", "fileId2"]; - var tags = ["tag1", "tag2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/removeTags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.bulkRemoveTags(fileIds, tags, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Copy file', function (done) { - var sourceFilePath = "/file_path.jpg"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/copy`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.copyFile({ sourceFilePath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Move file', function (done) { - var sourceFilePath = "/file_path.jpg"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/move`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.moveFile({ sourceFilePath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Rename file', function (done) { - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/rename`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.renameFile({ - filePath: "/xyz.jpg", - newFileName: "test.jpg" - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Restore file version', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/${fileId}/versions/${versionId}/restore`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.restoreFileVersion({ - fileId, - versionId - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Copy folder', function (done) { - var sourceFolderPath = "/folder2"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/copyFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.copyFolder({ sourceFolderPath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Move folder', function (done) { - var sourceFolderPath = "/folder1"; - var destinationPath = "/folder2/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/moveFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.moveFolder({ sourceFolderPath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Get bulk job status', function (done) { - var jobId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/bulkJobs/${jobId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.getBulkJobStatus(jobId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Create folder', function (done) { - var folderName = "folder1"; - var parentFolderPath = "/"; - - const scope = nock('https://api.imagekit.io') - .post('/v1/folder') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(201, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.createFolder({ folderName, parentFolderPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - - it('Delete folder', function (done) { - var folderPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/folder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(204, dummyAPISuccessResponse) - - var callback = sinon.spy(); - - imagekit.deleteFolder(folderPath, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, dummyAPISuccessResponse); - done(); - }, 50); - }); - }); - - describe("Error callbacks", function () { - it('Delete single file', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/files/${fileId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - imagekit.deleteFile(fileId, callback) - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Get file metadata using fileId', function (done) { - var fileId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/metadata`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.getFileMetadata(fileId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Get file metadata using remote URL', function (done) { - var url = "https://ik.imagekit.io/demo/image.jpg"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/metadata`) - .query({ - url - }) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.getFileMetadata(url, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Get file details', function (done) { - var fileId = "23902390239203923"; - - var callback = sinon.spy(); - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - imagekit.getFileDetails(fileId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Update file details', function (done) { - var fileId = "23902390239203923"; - - var updateData = { - tags: ["tag1", "tag2"], - customCoordinates: "10,10,100,100" - } - - var callback = sinon.spy(); - - const scope = nock('https://api.imagekit.io') - .patch(`/v1/files/${fileId}/details`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - imagekit.updateFileDetails(fileId, updateData, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('List files', function (done) { - var listOptions = { - skip: 0, - limit: 100 - } - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .query(listOptions) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.listFiles(listOptions, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Bulk file delete by fileids', function (done) { - var fileIds = ["fileId1", "fileId2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/batch/deleteByFileIds`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - - var callback = sinon.spy(); - - imagekit.bulkDeleteFiles(fileIds, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Bulk add tags', function (done) { - var fileIds = ["fileId1", "fileId2"]; - var tags = ["tag1", "tag2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/addTags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.bulkAddTags(fileIds, tags, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Bulk remove tags', function (done) { - var fileIds = ["fileId1", "fileId2"]; - var tags = ["tag1", "tag2"]; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/removeTags`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.bulkRemoveTags(fileIds, tags, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Copy file', function (done) { - var sourceFilePath = "/file_path.jpg"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/copy`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.copyFile({ sourceFilePath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Move file', function (done) { - var sourceFilePath = "/file_path.jpg"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/move`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.moveFile({ sourceFilePath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Rename file', function (done) { - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/rename`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.renameFile({ - filePath: "/xyz.jpg", - newFileName: "test.jpg" - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Restore file version', function (done) { - var fileId = "fileId"; - var versionId = "versionId"; - const scope = nock('https://api.imagekit.io') - .put(`/v1/files/${fileId}/versions/${versionId}/restore`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.restoreFileVersion({ - fileId, - versionId - }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Copy folder', function (done) { - var sourceFolderPath = "/folder2"; - var destinationPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/copyFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.copyFolder({ sourceFolderPath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Move folder', function (done) { - var sourceFolderPath = "/folder1"; - var destinationPath = "/folder2/"; - - const scope = nock('https://api.imagekit.io') - .post(`/v1/bulkJobs/moveFolder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.moveFolder({ sourceFolderPath, destinationPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Get bulk job status', function (done) { - var jobId = "23902390239203923"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/bulkJobs/${jobId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.getBulkJobStatus(jobId, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Create folder', function (done) { - var folderName = "folder1"; - var parentFolderPath = "/"; - - const scope = nock('https://api.imagekit.io') - .post('/v1/folder') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.createFolder({ folderName, parentFolderPath }, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Delete folder', function (done) { - var folderPath = "/folder1/"; - - const scope = nock('https://api.imagekit.io') - .delete(`/v1/folder`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(404, dummyAPIErrorResponse) - - var callback = sinon.spy(); - - imagekit.deleteFolder(folderPath, callback); - - setTimeout(function () { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, dummyAPIErrorResponse, null); - done(); - }, 50); - }); - - it('Rate limit error', function (done) { - var fileIds = ["fileId1", "fileId2"]; - - var responseBody = { - message: "rate limit exceeded" - }; - - var rateLimitHeaders = { - "X-RateLimit-Limit": 10, - "X-RateLimit-Reset": 1000, - "X-RateLimit-Interval": 1000 - } - - const scope = nock('https://api.imagekit.io') - .post(`/v1/files/batch/deleteByFileIds`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(() => { - return [ - 429, - responseBody, - rateLimitHeaders - ] - }) - - imagekit.bulkDeleteFiles(fileIds, function (err, response) { - expect(err).deep.equal({ - ...responseBody, - ...rateLimitHeaders - }) - done(); - }); - }); - }); -}); - diff --git a/tests/path.test.ts b/tests/path.test.ts new file mode 100644 index 00000000..7a5a43c2 --- /dev/null +++ b/tests/path.test.ts @@ -0,0 +1,462 @@ +import { createPathTagFunction, encodeURIPath } from '@imagekit/nodejs/internal/utils/path'; +import { inspect } from 'node:util'; +import { runInNewContext } from 'node:vm'; + +describe('path template tag function', () => { + test('validates input', () => { + const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E']; + const testCases = [ + ['/path_params/', '/a'], + ['/path_params/', '/'], + ['/path_params/', ''], + ['', '/a'], + ['', '/'], + ['', ''], + ['a'], + [''], + ['/path_params/', ':initiate'], + ['/path_params/', '.json'], + ['/path_params/', '?beta=true'], + ['/path_params/', '.?beta=true'], + ['/path_params/', '/', '/download'], + ['/path_params/', '-', '/download'], + ['/path_params/', '', '/download'], + ['/path_params/', '.', '/download'], + ['/path_params/', '..', '/download'], + ['/plain/path'], + ]; + + function paramPermutations(len: number): string[][] { + if (len === 0) return []; + if (len === 1) return testParams.map((e) => [e]); + const rest = paramPermutations(len - 1); + return testParams.flatMap((e) => rest.map((r) => [e, ...r])); + } + + // We need to test how %2E is handled, so we use a custom encoder that does no escaping. + const rawPath = createPathTagFunction((s) => s); + + const emptyObject = {}; + const mathObject = Math; + const numberObject = new Number(); + const stringObject = new String(); + const basicClass = new (class {})(); + const classWithToString = new (class { + toString() { + return 'ok'; + } + })(); + + // Invalid values + expect(() => rawPath`/a/${null}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Null is not a valid path parameter\n' + + '/a/null/b\n' + + ' ^^^^', + ); + expect(() => rawPath`/a/${undefined}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Undefined is not a valid path parameter\n' + + '/a/undefined/b\n' + + ' ^^^^^^^^^', + ); + expect(() => rawPath`/a/${emptyObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${mathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${basicClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^', + ); + expect(() => rawPath`/../${''}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/../\n' + + ' ^^', + ); + expect(() => rawPath`/../${{}}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + 'Value of type Object is not a valid path parameter\n' + + '/../[object Object]\n' + + ' ^^ ^^^^^^^^^^^^^^', + ); + + // Valid values + expect(rawPath`/${0}`).toBe('/0'); + expect(rawPath`/${''}`).toBe('/'); + expect(rawPath`/${numberObject}`).toBe('/0'); + expect(rawPath`${stringObject}/`).toBe('/'); + expect(rawPath`/${classWithToString}`).toBe('/ok'); + + // We need to check what happens with cross-realm values, which we might get from + // Jest or other frames in a browser. + + const newRealm = runInNewContext('globalThis'); + expect(newRealm.Object).not.toBe(Object); + + const crossRealmObject = newRealm.Object(); + const crossRealmMathObject = newRealm.Math; + const crossRealmNumber = new newRealm.Number(); + const crossRealmString = new newRealm.String(); + const crossRealmClass = new (class extends newRealm.Object {})(); + const crossRealmClassWithToString = new (class extends newRealm.Object { + toString() { + return 'ok'; + } + })(); + + // Invalid cross-realm values + expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/a/[object Object]/b\n' + + ' ^^^^^^^^^^^^^^^', + ); + expect(() => rawPath`?${crossRealmMathObject}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Math is not a valid path parameter\n' + + '?[object Math]\n' + + ' ^^^^^^^^^^^^^', + ); + expect(() => rawPath`/${crossRealmClass}`).toThrow( + 'Path parameters result in path with invalid segments:\n' + + 'Value of type Object is not a valid path parameter\n' + + '/[object Object]\n' + + ' ^^^^^^^^^^^^^^^', + ); + + // Valid cross-realm values + expect(rawPath`/${crossRealmNumber}`).toBe('/0'); + expect(rawPath`${crossRealmString}/`).toBe('/'); + expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); + + const results: { + [pathParts: string]: { + [params: string]: { valid: boolean; result?: string; error?: string }; + }; + } = {}; + + for (const pathParts of testCases) { + const pathResults: Record = {}; + results[JSON.stringify(pathParts)] = pathResults; + for (const params of paramPermutations(pathParts.length - 1)) { + const stringRaw = String.raw({ raw: pathParts }, ...params); + const plainString = String.raw( + { raw: pathParts.map((e) => e.replace(/\./g, 'x')) }, + ...params.map((e) => 'X'.repeat(e.length)), + ); + const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href; + const normalizedPlainString = new URL(plainString, 'https://example.com').href; + const pathResultsKey = JSON.stringify(params); + try { + const result = rawPath(pathParts, ...params); + expect(result).toBe(stringRaw); + // there are no special segments, so the length of the normalized path is + // equal to the length of the normalized plain path. + expect(normalizedStringRaw.length).toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: true, + result, + }; + } catch (e) { + const error = String(e); + expect(error).toMatch(/Path parameters result in path with invalid segment/); + // there are special segments, so the length of the normalized path is + // different than the length of the normalized plain path. + expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length); + pathResults[pathResultsKey] = { + valid: false, + error, + }; + } + } + } + + expect(results).toMatchObject({ + '["/path_params/","/a"]': { + '["x"]': { valid: true, result: '/path_params/x/a' }, + '[""]': { valid: true, result: '/path_params//a' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/a\n' + + ' ^^^^^^', + }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/a\n' + + ' ^^^', + }, + }, + '["/path_params/","/"]': { + '["x"]': { valid: true, result: '/path_params/x/' }, + '[""]': { valid: true, result: '/path_params//' }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E/\n' + + ' ^^^^^^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e/\n' + + ' ^^^', + }, + }, + '["/path_params/",""]': { + '[""]': { valid: true, result: '/path_params/' }, + '["x"]': { valid: true, result: '/path_params/x' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E\n' + + ' ^^^', + }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e\n' + + ' ^^^^^^', + }, + }, + '["","/a"]': { + '[""]': { valid: true, result: '/a' }, + '["x"]': { valid: true, result: 'x/a' }, + '["%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '%2e%2E/a\n' + + '^^^^^^', + }, + }, + '["","/"]': { + '["x"]': { valid: true, result: 'x/' }, + '[""]': { valid: true, result: '/' }, + '["%2E%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + '%2E%2e/\n' + + '^^^^^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + './\n^', + }, + }, + '["",""]': { + '[""]': { valid: true, result: '' }, + '["x"]': { valid: true, result: 'x' }, + '[".."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '..\n^^', + }, + '["."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '.\n^', + }, + }, + '["a"]': {}, + '[""]': {}, + '["/path_params/",":initiate"]': { + '[""]': { valid: true, result: '/path_params/:initiate' }, + '["."]': { valid: true, result: '/path_params/.:initiate' }, + }, + '["/path_params/",".json"]': { + '["x"]': { valid: true, result: '/path_params/x.json' }, + '["."]': { valid: true, result: '/path_params/..json' }, + }, + '["/path_params/","?beta=true"]': { + '["x"]': { valid: true, result: '/path_params/x?beta=true' }, + '[""]': { valid: true, result: '/path_params/?beta=true' }, + '["%2E%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2E?beta=true\n' + + ' ^^^^^^', + }, + '["%2e%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2e%2E?beta=true\n' + + ' ^^^^^^', + }, + }, + '["/path_params/",".?beta=true"]': { + '[".."]': { valid: true, result: '/path_params/...?beta=true' }, + '["x"]': { valid: true, result: '/path_params/x.?beta=true' }, + '[""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/.?beta=true\n' + + ' ^', + }, + '["%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2e." can\'t be safely passed as a path parameter\n' + + '/path_params/%2e.?beta=true\n' + + ' ^^^^', + }, + }, + '["/path_params/","/","/download"]': { + '["",""]': { valid: true, result: '/path_params///download' }, + '["","x"]': { valid: true, result: '/path_params//x/download' }, + '[".","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/./%2e/download\n' + + ' ^ ^^^', + }, + '["%2E%2e","%2e"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + + 'Value "%2e" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E%2e/%2e/download\n' + + ' ^^^^^^ ^^^', + }, + }, + '["/path_params/","-","/download"]': { + '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' }, + }, + '["/path_params/","","/download"]': { + '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' }, + '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' }, + '["","%2E"]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E" can\'t be safely passed as a path parameter\n' + + '/path_params/%2E/download\n' + + ' ^^^', + }, + '["%2E","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "%2E." can\'t be safely passed as a path parameter\n' + + '/path_params/%2E./download\n' + + ' ^^^^', + }, + }, + '["/path_params/",".","/download"]': { + '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' }, + '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value "." can\'t be safely passed as a path parameter\n' + + '/path_params/./download\n' + + ' ^', + }, + '["","."]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + '["/path_params/","..","/download"]': { + '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' }, + '["","x"]': { valid: true, result: '/path_params/..x/download' }, + '["",""]': { + valid: false, + error: + 'Error: Path parameters result in path with invalid segments:\n' + + 'Value ".." can\'t be safely passed as a path parameter\n' + + '/path_params/../download\n' + + ' ^^', + }, + }, + }); + }); +}); + +describe('encodeURIPath', () => { + const testCases: string[] = [ + '', + // Every ASCII character + ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)), + // Unicode BMP codepoint + 'å', + // Unicode supplementary codepoint + '😃', + ]; + + for (const param of testCases) { + test('properly encodes ' + inspect(param), () => { + const encoded = encodeURIPath(param); + const naiveEncoded = encodeURIComponent(param); + // we should never encode more characters than encodeURIComponent + expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length); + expect(decodeURIComponent(encoded)).toBe(param); + }); + } + + test("leaves ':' intact", () => { + expect(encodeURIPath(':')).toBe(':'); + }); + + test("leaves '@' intact", () => { + expect(encodeURIPath('@')).toBe('@'); + }); +}); diff --git a/tests/phash.js b/tests/phash.js deleted file mode 100644 index 5699a3a7..00000000 --- a/tests/phash.js +++ /dev/null @@ -1,93 +0,0 @@ -import chai from "chai"; -const initializationParams = require("./data").initializationParams -import ImageKit from "../index"; -var imagekit = new ImageKit(initializationParams); - -// helpers -const errors = require('./helpers/errors'); -const spies = require('./helpers/spies'); - -const { expect } = chai; -const { pHashDistanceSpy } = spies; - -const failureHelper = (expectedError, ...params) => { - const { message, help } = expectedError; - const { message: error } = imagekit.pHashDistance(...params); - - expect(error).to.be.equal(`${message}: ${help}`); -}; - -const successHelper = (distance, ...params) => { - const result = imagekit.pHashDistance(...params); - - expect(result).to.be.a('number'); - expect(result).to.equal(distance); - expect(pHashDistanceSpy.calledOnceWithExactly(...params)).to.equal(true); -}; - -const pHash = { - invalidAlphabeticalString: 'INVALIDHEXSTRING', - invalidCharacterString: 'a4a655~!!@94518b', - invalidHexStringLength: '42', - numeric: 2222222222222222, - valid: 'f06830ca9f1e3e90', - // sets - dissimilar: [ - 'a4a65595ac94518b', - '7838873e791f8400', - ], - similar: [ - '2d5ad3936d2e015b', - '2d6ed293db36a4fb', - ], -}; - -describe('Utils > pHash > Distance calculator', () => { - beforeEach(() => { - pHashDistanceSpy.resetHistory(); - }); - - after(() => { - pHashDistanceSpy.resetHistory(); - }); - - context('Failure cases:', () => { - it('Should return error for missing first pHash', () => { - failureHelper(errors.MISSING_PHASH_VALUE, null, pHash.valid); - }); - - it('Should return error for missing second pHash', () => { - failureHelper(errors.MISSING_PHASH_VALUE, pHash.valid); - }); - - it('Should return error for invalid first pHash', () => { - failureHelper(errors.INVALID_PHASH_VALUE, pHash.invalidAlphabeticalString, pHash.valid); - }); - - it('Should return error for invalid second pHash', () => { - failureHelper(errors.INVALID_PHASH_VALUE, pHash.valid, pHash.invalidCharacterString); - }); - - it('Should return error for unequal pHash lengths', () => { - failureHelper(errors.UNEQUAL_STRING_LENGTH, pHash.valid, pHash.invalidHexStringLength); - }); - }); - - context('Success cases:', () => { - it('Should return zero distance between pHash for same image', () => { - successHelper(0, pHash.valid, pHash.valid); - }); - - it('Should return smaller distance between pHash for similar images', () => { - successHelper(17, pHash.similar[0], pHash.similar[1]); - }); - - it('Should return larger distance between pHash for dissimilar images', () => { - successHelper(37, pHash.dissimilar[0], pHash.dissimilar[1]); - }); - - it('Should return distance for non-string but valid hexanumeric pHash', () => { - successHelper(30, pHash.valid, pHash.numeric); - }); - }); -}); \ No newline at end of file diff --git a/tests/response-metadata.js b/tests/response-metadata.js deleted file mode 100644 index 6ca30796..00000000 --- a/tests/response-metadata.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Only checking for one API success and error. Assuing that all API uses same underlying request util - */ - -import chai from "chai"; -import sinon from "sinon"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams - -import ImageKit from "../index"; -import nock from "nock"; -var imagekit = new ImageKit(initializationParams); - -const dummyAPISuccessResponse = { - dummyKey: "dummyValue" -}; - -const dummyAPIErrorResponse = { - help: "help", - message: "message" -} - -const dummyAPIErrorResponseString = "Internal server error" - -const responseHeaders = { - 'x-request-id': "request-id" -} - -describe("Promise", function () { - it('Success', async function () { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, dummyAPISuccessResponse, responseHeaders) - - var response = await imagekit.getPurgeCacheStatus(requestId); - expect(response).to.be.deep.equal(dummyAPISuccessResponse); - expect(response.$ResponseMetadata.statusCode).to.be.equal(200); - expect(response.$ResponseMetadata.headers).to.be.deep.equal({ - ...responseHeaders, - 'content-type': 'application/json' - }); - return Promise.resolve(); - }); - - it('Server handled error', async function () { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponse, responseHeaders) - - try { - await imagekit.getPurgeCacheStatus(requestId); - } catch (ex) { - expect(ex).to.be.deep.equal(dummyAPIErrorResponse); - expect(ex.$ResponseMetadata.statusCode).to.be.equal(500); - expect(ex.$ResponseMetadata.headers).to.be.deep.equal({ - ...responseHeaders, - 'content-type': 'application/json' - }); - return Promise.resolve(); - } - - return Promise.reject(); - }); - - it('Server unhandled error', async function () { - var requestId = "sdfdsfksjfldsjfjsdf"; - - const scope = nock('https://api.imagekit.io') - .get(`/v1/files/purge/${requestId}`) - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(500, dummyAPIErrorResponseString, responseHeaders) - - try { - await imagekit.getPurgeCacheStatus(requestId); - } catch (ex) { - expect(ex).to.be.deep.equal({ - help: dummyAPIErrorResponseString - }); - expect(ex.$ResponseMetadata.statusCode).to.be.equal(500); - expect(ex.$ResponseMetadata.headers).to.be.deep.equal({ - ...responseHeaders - }); - return Promise.resolve(); - } - - return Promise.reject(); - }); -}); \ No newline at end of file diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts new file mode 100644 index 00000000..c3193151 --- /dev/null +++ b/tests/stringifyQuery.test.ts @@ -0,0 +1,29 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ImageKit } from '@imagekit/nodejs'; + +const { stringifyQuery } = ImageKit.prototype as any; + +describe(stringifyQuery, () => { + for (const [input, expected] of [ + [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], + [{ a: null, b: false, c: undefined }, 'a=&b=false'], + [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], + [ + { 'a/b': 'c/d', 'e=f': 'g&h' }, + `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( + 'e=f', + )}=${encodeURIComponent('g&h')}`, + ], + ]) { + it(`${JSON.stringify(input)} -> ${expected}`, () => { + expect(stringifyQuery(input)).toEqual(expected); + }); + } + + for (const value of [[], {}, new Date()]) { + it(`${JSON.stringify(value)} -> `, () => { + expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); + }); + } +}); diff --git a/tests/unit.js b/tests/unit.js deleted file mode 100644 index 61be7331..00000000 --- a/tests/unit.js +++ /dev/null @@ -1,78 +0,0 @@ -import chai from "chai"; -const expect = chai.expect; -import ImageKit from "../index"; - -import urlBuilder from "../libs/url/builder"; - -describe("Unit test cases", function () { - var imagekit = new ImageKit({ - publicKey: "public_key_test", - privateKey: "private_key_test", - urlEndpoint: "https://test-domain.com/test-endpoint" - }); - - it('Authentication params check', function () { - var authenticationParameters = imagekit.getAuthenticationParameters("your_token", 1582269249); - expect(authenticationParameters).to.deep.equal({ - token: 'your_token', - expire: 1582269249, - signature: 'e71bcd6031016b060d349d212e23e85c791decdd' - }) - }); - - it('Authentication params check no params', function () { - var authenticationParameters = imagekit.getAuthenticationParameters(); - expect(authenticationParameters).to.have.property("token"); - expect(authenticationParameters).to.have.property("expire"); - expect(authenticationParameters).to.have.property("signature"); - }); - - it('Signed URL signature without slash default expiry', function () { - var url = "https://test-domain.com/test-endpoint/tr:w-100/test-signed-url.png"; - var signature = urlBuilder.getSignature({ - privateKey: "private_key_test", - url: url, - urlEndpoint:"https://test-domain.com/test-endpoint", - expiryTimestamp: "9999999999" - }) - expect(signature).to.be.equal("41b3075c40bc84147eb71b8b49ae7fbf349d0f00") - }); - - it('Signed URL signature with slash default expiry', function () { - var url = "https://test-domain.com/test-endpoint/tr:w-100/test-signed-url.png"; - var signature = urlBuilder.getSignature({ - privateKey: "private_key_test", - url: url, - urlEndpoint:"https://test-domain.com/test-endpoint/", - expiryTimestamp: "9999999999" - }) - expect(signature).to.be.equal("41b3075c40bc84147eb71b8b49ae7fbf349d0f00") - }); - - it('Signed URL signature empty', function () { - var url = "https://test-domain.com/test-endpoint/tr:w-100/test-signed-url.png"; - var signature = urlBuilder.getSignature({ - }) - expect(signature).to.be.equal("") - }); - - it('pHash distance different', function () { - var pHashDistance = imagekit.pHashDistance("33699c96619cc69e","968e978414fe04ea"); - expect(pHashDistance).to.be.equal(30) - }); - - it('pHash distance similar', function () { - var pHashDistance = imagekit.pHashDistance("63433b3ccf8e1ebe","f5d2226cd9d32b16"); - expect(pHashDistance).to.be.equal(27) - }); - - it('pHash distance similar reverse', function () { - var pHashDistance = imagekit.pHashDistance("f5d2226cd9d32b16","63433b3ccf8e1ebe"); - expect(pHashDistance).to.be.equal(27) - }); - - it('pHash distance same', function () { - var pHashDistance = imagekit.pHashDistance("33699c96619cc69e","33699c96619cc69e"); - expect(pHashDistance).to.be.equal(0) - }); -}); \ No newline at end of file diff --git a/tests/upload.js b/tests/upload.js deleted file mode 100644 index 935c6f18..00000000 --- a/tests/upload.js +++ /dev/null @@ -1,599 +0,0 @@ -import chai from "chai"; -import sinon from "sinon"; -const expect = chai.expect; -const initializationParams = require("./data").initializationParams -import ImageKit from "../index"; -import nock from "nock"; -import fs from "fs"; -import path from "path"; - -function checkFormData({requestBody, boundary, fieldName, fieldValue}) { - return expect(requestBody).include(`${boundary}\r\nContent-Disposition: form-data; name="${fieldName}"\r\n\r\n${fieldValue}`) -} - -const uploadSuccessResponseObj = { - "fileId": "598821f949c0a938d57563bd", - "name": "file1.jpg", - "url": "https://ik.imagekit.io/your_imagekit_id/images/products/file1.jpg", - "thumbnailUrl": "https://ik.imagekit.io/your_imagekit_id/tr:n-media_library_thumbnail/images/products/file1.jpg", - "height": 300, - "width": 200, - "size": 83622, - "filePath": "/images/products/file1.jpg", - "tags": ["t-shirt", "round-neck", "sale2019"], - "isPrivateFile": false, - "customCoordinates": null, - "fileType": "image", - "AITags":[{"name":"Face","confidence":99.95,"source":"aws-auto-tagging"}], - "extensionStatus":{"aws-auto-tagging":"success"} -}; - -describe("File upload custom endpoint", function () { - var imagekit = new ImageKit({ - ...initializationParams, - uploadEndpoint: "https://custom-env.imagekit.io/api/v1/files/upload" - }); - - it('Upload endpoint test case', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - var callback = sinon.spy(); - - const scope = nock('https://custom-env.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, uploadSuccessResponseObj) - - imagekit.upload(fileOptions, callback); - - setTimeout( () => { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); - done(); - },10); - }); -}); - -describe("File upload", function () { - var imagekit = new ImageKit(initializationParams); - - it('Invalid upload params', function () { - var callback = sinon.spy(); - - imagekit.upload(null, callback); - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, { help: "", message: "Missing data for upload" }, null); - }); - - it('Missing fileName', function () { - const fileOptions = { - file: "https://ik.imagekit.io/remote-url.jpg" - }; - - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, { help: "", message: "Missing fileName parameter for upload" }, null); - }); - - it('Missing file', function () { - const fileOptions = { - fileName: "test_file_name", - }; - - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, { help: "", message: "Missing file parameter for upload" }, null); - }); - - it('Full request', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - tags: ["tag1","tag2"], // array handling - isPrivateFile: true, // Boolean handling - useUniqueFileName: "false", // As string - responseFields: ["tags", "metadata"], - extensions: [ - { - name: "aws-auto-tagging", - minConfidence: 80, - maxTags: 10 - } - ], - webhookUrl: "https://your-domain/?appId=some-id", - overwriteFile: true, - overwriteAITags: false, - overwriteTags: true, - overwriteCustomMetadata: false, - customMetadata: { - brand: "Nike", - color: "red" - }, - }; - - var callback = sinon.spy(); - var jsonStringifiedExtensions = JSON.stringify(fileOptions.extensions); - const customMetadata = JSON.stringify(fileOptions.customMetadata); - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - checkFormData({requestBody,boundary,fieldName:"fileName",fieldValue:fileOptions.fileName}); - checkFormData({requestBody,boundary,fieldName:"file",fieldValue:fileOptions.file}); - checkFormData({requestBody,boundary,fieldName:"tags",fieldValue:"tag1,tag2"}); - checkFormData({requestBody,boundary,fieldName:"isPrivateFile",fieldValue:"true"}); - checkFormData({requestBody,boundary,fieldName:"useUniqueFileName",fieldValue:"false"}); - checkFormData({requestBody,boundary,fieldName:"responseFields",fieldValue:"tags,metadata"}); - checkFormData({requestBody,boundary,fieldName:"extensions",fieldValue:jsonStringifiedExtensions}); - checkFormData({requestBody,boundary,fieldName:"webhookUrl",fieldValue:"https://your-domain/?appId=some-id"}); - checkFormData({requestBody,boundary,fieldName:"overwriteFile",fieldValue:"true"}); - checkFormData({requestBody,boundary,fieldName:"overwriteAITags",fieldValue:"false"}); - checkFormData({requestBody,boundary,fieldName:"overwriteTags",fieldValue:"true"}); - checkFormData({requestBody,boundary,fieldName:"overwriteCustomMetadata",fieldValue:"false"}); - checkFormData({requestBody,boundary,fieldName:"customMetadata",fieldValue:customMetadata}); - done() - }) - - imagekit.upload(fileOptions, callback); - }); - - it('Buffer file smaller than 10MB', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: fs.readFileSync(path.join(__dirname,"./data/test_image.jpg")) - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - expect(requestBody.length).equal(399064); - done() - }) - - imagekit.upload(fileOptions); - }); - - it('Buffer file larger than 10MB', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: Buffer.alloc(15000000), // static buffer of 15 MB size - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - expect(requestBody.length).equal(15000347); - }) - - imagekit.upload(fileOptions, function (err, result) { - expect(err).to.equal(null) - done(); - }); - }); - - it('Missing useUniqueFileName', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - isPrivateFile: true - }; - - var callback = sinon.spy(); - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - checkFormData({requestBody,boundary,fieldName:"fileName",fieldValue:fileOptions.fileName}); - checkFormData({requestBody,boundary,fieldName:"file",fieldValue:fileOptions.file}); - checkFormData({requestBody,boundary,fieldName:"isPrivateFile",fieldValue:"true"}); - expect(requestBody).to.not.include("useUniqueFileName"); - done() - }) - - imagekit.upload(fileOptions, callback); - }); - - it('Missing isPrivateFile and useUniqueFileName', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - tags: "tag1,tag2" // as string - }; - - var callback = sinon.spy(); - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - checkFormData({requestBody,boundary,fieldName:"fileName",fieldValue:fileOptions.fileName}); - checkFormData({requestBody,boundary,fieldName:"file",fieldValue:fileOptions.file}); - checkFormData({requestBody,boundary,fieldName:"tags",fieldValue:"tag1,tag2"}); - expect(requestBody).to.not.include("useUniqueFileName"); - expect(requestBody).to.not.include("isPrivateFile"); - done() - }) - - imagekit.upload(fileOptions, callback); - }); - - it('Bare minimum request', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - var callback = sinon.spy(); - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=",""); - checkFormData({requestBody,boundary,fieldName:"fileName",fieldValue:fileOptions.fileName}); - checkFormData({requestBody,boundary,fieldName:"file",fieldValue:fileOptions.file}); - expect(requestBody).to.not.include("tags"); - expect(requestBody).to.not.include("useUniqueFileName"); - expect(requestBody).to.not.include("isPrivateFile"); - expect(requestBody).to.not.include("customCoordinates"); - expect(requestBody).to.not.include("responseFields"); - done() - }) - - imagekit.upload(fileOptions, callback); - }); - - it('Success callback', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - var callback = sinon.spy(); - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, uploadSuccessResponseObj) - - imagekit.upload(fileOptions, callback); - - setTimeout( () => { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); - done(); - },10); - }); - - it('Success using promise', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(200, uploadSuccessResponseObj) - - imagekit.upload(fileOptions) - .then((response, error) => { - expect(response).to.been.deep.equal(uploadSuccessResponseObj) - done(); - }); - }); - - it('Network error', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .replyWithError("Network error occured") - - imagekit.upload(fileOptions, function(err, response) { - expect(err.message).equal("Network error occured"); - done(); - }); - }); - - it('Server side error', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - var callback = sinon.spy(); - - var error = { - help: "For support kindly contact us at support@imagekit.io .", - message: "Your account cannot be authenticated." - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(403, error) - - imagekit.upload(fileOptions, callback); - - setTimeout( () => { - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, error, null); - done(); - },10); - }); - - it('Server side error promise', function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content" - }; - - var error = { - help: "For support kindly contact us at support@imagekit.io .", - message: "Your account cannot be authenticated." - }; - - const scope = nock('https://upload.imagekit.io/api') - .post('/v1/files/upload') - .basicAuth({ user: initializationParams.privateKey, pass: '' }) - .reply(403, error) - - imagekit.upload(fileOptions) - .then((response, error) => { - }) - .catch(error => { - expect(error).to.been.deep.equal(error) - done(); - }) - }); - - it("With pre and post transformation", function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - transformation: { pre: "w-100", post: [{ type: "transformation", value: "h-100" }] }, - }; - - var callback = sinon.spy(); - const transformation = JSON.stringify(fileOptions.transformation); - - const scope = nock("https://upload.imagekit.io/api") - .post("/v1/files/upload") - .basicAuth({ user: initializationParams.privateKey, pass: "" }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=", ""); - checkFormData({ requestBody, boundary, fieldName: "fileName", fieldValue: fileOptions.fileName }); - checkFormData({ requestBody, boundary, fieldName: "file", fieldValue: fileOptions.file }); - checkFormData({ requestBody, boundary, fieldName: "transformation", fieldValue: transformation }); - done(); - }); - - imagekit.upload(fileOptions, callback); - }); - - it("With pre transformation", function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - transformation: { pre: "w-100" }, - }; - - var callback = sinon.spy(); - const transformation = JSON.stringify(fileOptions.transformation); - - const scope = nock("https://upload.imagekit.io/api") - .post("/v1/files/upload") - .basicAuth({ user: initializationParams.privateKey, pass: "" }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=", ""); - checkFormData({ requestBody, boundary, fieldName: "fileName", fieldValue: fileOptions.fileName }); - checkFormData({ requestBody, boundary, fieldName: "file", fieldValue: fileOptions.file }); - checkFormData({ requestBody, boundary, fieldName: "transformation", fieldValue: transformation }); - done(); - }); - - imagekit.upload(fileOptions, callback); - }); - - it("With post transformation", function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - transformation: { post: [{ type: "transformation", value: "h-100" }] }, - }; - - var callback = sinon.spy(); - const transformation = JSON.stringify(fileOptions.transformation); - - const scope = nock("https://upload.imagekit.io/api") - .post("/v1/files/upload") - .basicAuth({ user: initializationParams.privateKey, pass: "" }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=", ""); - checkFormData({ requestBody, boundary, fieldName: "fileName", fieldValue: fileOptions.fileName }); - checkFormData({ requestBody, boundary, fieldName: "file", fieldValue: fileOptions.file }); - checkFormData({ requestBody, boundary, fieldName: "transformation", fieldValue: transformation }); - done(); - }); - - imagekit.upload(fileOptions, callback); - }); - - it("Should return error for an invalid transformation", async function () { - const fileOptions = { - fileName: "test_file_name", - file: "test_file", - responseFields: "tags, customCoordinates, isPrivateFile, metadata", - useUniqueFileName: false, - transformation: {}, - }; - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - - var errRes = { - help: "", - message: "Invalid transformation parameter. Please include at least pre, post, or both.", - }; - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, errRes, null); - }); - - it("Should return error for an invalid pre transformation", async function () { - const fileOptions = { - fileName: "test_file_name", - file: "test_file", - responseFields: "tags, customCoordinates, isPrivateFile, metadata", - useUniqueFileName: false, - transformation: { pre: "" }, - }; - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - - var errRes = { - help: "", - message: "Invalid pre transformation parameter.", - }; - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, errRes, null); - }); - - it("Should return error for an invalid post transformation of type abs", async function () { - const fileOptions = { - fileName: "test_file_name", - file: "test_file", - responseFields: "tags, customCoordinates, isPrivateFile, metadata", - useUniqueFileName: false, - transformation: { post: [{ type: "abs", value: "" }] }, - }; - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - - var errRes = { - help: "", - message: "Invalid post transformation parameter.", - }; - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, errRes, null); - }); - - it("Should return error for an invalid post transformation of type transformation", async function () { - const fileOptions = { - fileName: "test_file_name", - file: "test_file", - responseFields: "tags, customCoordinates, isPrivateFile, metadata", - useUniqueFileName: false, - transformation: { post: [{ type: "transformation", value: "" }] }, - }; - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - - var errRes = { - help: "", - message: "Invalid post transformation parameter.", - }; - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, errRes, null); - }); - - it("Should return error for an invalid post transformation if it's not an array", async function () { - const fileOptions = { - fileName: "test_file_name", - file: "test_file", - responseFields: "tags, customCoordinates, isPrivateFile, metadata", - useUniqueFileName: false, - transformation: { post: {} }, - }; - var callback = sinon.spy(); - - imagekit.upload(fileOptions, callback); - - var errRes = { - help: "", - message: "Invalid post transformation parameter.", - }; - expect(callback.calledOnce).to.be.true; - sinon.assert.calledWith(callback, errRes, null); - }); - - it("With checks option", function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - checks: "'request.folder' : '/'", - }; - - var callback = sinon.spy(); - - const scope = nock("https://upload.imagekit.io/api") - .post("/v1/files/upload") - .basicAuth({ user: initializationParams.privateKey, pass: "" }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=", ""); - checkFormData({ requestBody, boundary, fieldName: "fileName", fieldValue: fileOptions.fileName }); - checkFormData({ requestBody, boundary, fieldName: "file", fieldValue: fileOptions.file }); - checkFormData({ requestBody, boundary, fieldName: "checks", fieldValue: fileOptions.checks }); - done(); - }); - - imagekit.upload(fileOptions, callback); - }); - - it("With isPublished option", function (done) { - const fileOptions = { - fileName: "test_file_name", - file: "test_file_content", - isPublished: false - }; - - var callback = sinon.spy(); - - const scope = nock("https://upload.imagekit.io/api") - .post("/v1/files/upload") - .basicAuth({ user: initializationParams.privateKey, pass: "" }) - .reply(200, function (uri, requestBody) { - expect(this.req.headers["content-type"]).include("multipart/form-data; boundary=---------------------"); - var boundary = this.req.headers["content-type"].replace("multipart/form-data; boundary=", ""); - checkFormData({ requestBody, boundary, fieldName: "fileName", fieldValue: fileOptions.fileName }); - checkFormData({ requestBody, boundary, fieldName: "file", fieldValue: fileOptions.file }); - checkFormData({ requestBody, boundary, fieldName: "isPublished", fieldValue: fileOptions.isPublished }); - done(); - }); - - imagekit.upload(fileOptions, callback); - }); -}); diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts new file mode 100644 index 00000000..80ccae1b --- /dev/null +++ b/tests/uploads.test.ts @@ -0,0 +1,107 @@ +import fs from 'fs'; +import type { ResponseLike } from '@imagekit/nodejs/internal/to-file'; +import { toFile } from '@imagekit/nodejs/core/uploads'; +import { File } from 'node:buffer'; + +class MyClass { + name: string = 'foo'; +} + +function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { + return { + url, + blob: async () => content || new Blob([]), + }; +} + +describe('toFile', () => { + it('throws a helpful error for mismatched types', async () => { + await expect( + // @ts-expect-error intentionally mismatched type + toFile({ foo: 'string' }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, + ); + + await expect( + // @ts-expect-error intentionally mismatched type + toFile(new MyClass()), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, + ); + }); + + it('disallows string at the type-level', async () => { + // @ts-expect-error we intentionally do not type support for `string` + // to help people avoid passing a file path + const file = await toFile('contents'); + expect(file.text()).resolves.toEqual('contents'); + }); + + it('extracts a file name from a Response', async () => { + const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); + const file = await toFile(response); + expect(file.name).toEqual('audio.mp3'); + }); + + it('extracts a file name from a File', async () => { + const input = new File(['foo'], 'input.jsonl'); + const file = await toFile(input); + expect(file.name).toEqual('input.jsonl'); + }); + + it('extracts a file name from a ReadStream', async () => { + const input = fs.createReadStream('tests/uploads.test.ts'); + const file = await toFile(input); + expect(file.name).toEqual('uploads.test.ts'); + }); + + it('does not copy File objects', async () => { + const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); + const file = await toFile(input); + expect(file).toBe(input); + expect(file.name).toEqual('input.jsonl'); + expect(file.type).toBe('jsonl'); + }); + + it('is assignable to File and Blob', async () => { + const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); + const result = await toFile(input); + const file: File = result; + const blob: Blob = result; + void file, blob; + }); +}); + +describe('missing File error message', () => { + let prevGlobalFile: unknown; + let prevNodeFile: unknown; + beforeEach(() => { + // The file shim captures the global File object when it's first imported. + // Reset modules before each test so we can test the error thrown when it's undefined. + jest.resetModules(); + const buffer = require('node:buffer'); + // @ts-ignore + prevGlobalFile = globalThis.File; + prevNodeFile = buffer.File; + // @ts-ignore + globalThis.File = undefined; + buffer.File = undefined; + }); + afterEach(() => { + // Clean up + // @ts-ignore + globalThis.File = prevGlobalFile; + require('node:buffer').File = prevNodeFile; + jest.resetModules(); + }); + + test('is thrown', async () => { + const uploads = await import('@imagekit/nodejs/core/uploads'); + await expect( + uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), + ).rejects.toMatchInlineSnapshot( + `[Error: \`File\` is not defined as a global, which is required for file uploads.]`, + ); + }); +}); diff --git a/tests/url-generation.js b/tests/url-generation.js deleted file mode 100644 index 689e234e..00000000 --- a/tests/url-generation.js +++ /dev/null @@ -1,446 +0,0 @@ -import chai from "chai"; -const pkg = require("../package.json"); -const expect = chai.expect; -const initializationParams = require("./data").initializationParams -import ImageKit from "../index"; -import { encodeStringIfRequired, getSignature } from "../libs/url/builder"; -var imagekit = new ImageKit(initializationParams); - -describe("URL generation", function () { - it('no path no src', function () { - const url = imagekit.url({}); - - expect(url).equal(""); - }); - - it('no transformation path', function () { - const url = imagekit.url({ - path: "/test_path.jpg" - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg`); - }); - - it('no transformation src', function () { - const url = imagekit.url({ - src: "https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg" - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg`); - }); - - it('Undefined parameters with path', function () { - const url = imagekit.url({ - path: "/test_path_alt.jpg", - transformation: undefined, - transformationPosition: undefined, - src: undefined, - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg`); - }); - - it('Signed URL', function () { - const url = imagekit.url({ - path: "/test_path_alt.jpg", - signed: true - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?ik-s=e26ca157df99b30b2443d7cb6886fc396fb4c87b`); - }); - - it('Signed URL with expireSeconds', function () { - const url = imagekit.url({ - path: "/test_path_alt.jpg", - signed: true, - expireSeconds: 100 - }); - - expect(url).includes(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg`); - expect(url).includes(`ik-s=`); - }); - - it("Signed URL with é in filename", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/test_é_path_alt.jpg"; - const encodedUrl = encodeStringIfRequired(testURL); - expect(encodedUrl).equal("https://ik.imagekit.io/test_url_endpoint/test_%C3%A9_path_alt.jpg"); - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/test_é_path_alt.jpg", - signed: true, - }); - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_%C3%A9_path_alt.jpg?ik-s=${signature}`); - }); - - it("Signed URL with é in filename and path", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/aéb/test_é_path_alt.jpg"; - const encodedUrl = encodeStringIfRequired(testURL); - expect(encodedUrl).equal("https://ik.imagekit.io/test_url_endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg"); - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/aéb/test_é_path_alt.jpg", - signed: true, - }); - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg?ik-s=${signature}`); - }); - - it("Signed URL with é in filename, path and transformation as path", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Imagekité,fs-50,l-end/aéb/test_é_path_alt.jpg"; - const encodedUrl = encodeStringIfRequired(testURL); - expect(encodedUrl).equal("https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Imagekit%C3%A9,fs-50,l-end/a%C3%A9b/test_%C3%A9_path_alt.jpg"); - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - - const url = imagekit.url({ - path: "/aéb/test_é_path_alt.jpg", - signed: true, - transformation: [{ raw: "l-text,i-Imagekité,fs-50,l-end" }], - transformationPosition: "path", - }); - expect(url).equal( - `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Imagekit%C3%A9,fs-50,l-end/a%C3%A9b/test_%C3%A9_path_alt.jpg?ik-s=${signature}` - ); - }); - - it("Signed URL with é in filename, path and transformation as query", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/aéb/test_é_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end"; - const encodedUrl = encodeStringIfRequired(testURL); - expect(encodedUrl).equal("https://ik.imagekit.io/test_url_endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end"); - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/aéb/test_é_path_alt.jpg", - signed: true, - transformation: [{ raw: "l-text,i-Imagekité,fs-50,l-end" }], - transformationPosition: "query", - }); - expect(url).equal( - `https://ik.imagekit.io/test_url_endpoint/a%C3%A9b/test_%C3%A9_path_alt.jpg?tr=l-text%2Ci-Imagekit%C3%A9%2Cfs-50%2Cl-end&ik-s=${signature}` - ); - }); - - - it('should generate the correct url with path param', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400" - }] - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400/test_path.jpg`); - }); - - it('should generate the correct url with path param with multiple leading slash', function () { - const url = imagekit.url({ - path: "///test_path.jpg", - transformation: [{ - "height": "300", - "width": "400" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400/test_path.jpg`); - - }); - - it('should generate the correct url with path param with overidden urlEndpoint', function () { - const url = imagekit.url({ - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint_alt", - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint_alt/tr:h-300,w-400/test_path.jpg`); - - }); - - it('should generate the correct url with path param with transformationPosition as query', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformationPosition: "query", - transformation: [{ - "height": "300", - "width": "400" - }] - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300%2Cw-400`); - }); - - it('should generate the correct url with src param', function () { - const url = imagekit.url({ - src: "https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg", - transformation: [{ - "height": "300", - "width": "400" - }] - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300%2Cw-400`); - }); - - it('should generate the correct url with transformationPosition as query', function () { - const url = imagekit.url({ - src: "https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg", - transformationPosition: "query", - transformation: [{ - "height": "300", - "width": "400" - }] - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300%2Cw-400`); - }); - - it('should generate the correct url with query params properly merged', function () { - const url = imagekit.url({ - src: "https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?t1=v1", - queryParameters: { t2: "v2", t3: "v3" }, - transformation: [{ - "height": "300", - "width": "400" - }] - }); - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?t1=v1&t2=v2&t3=v3&tr=h-300%2Cw-400`); - }) - - - it('should generate the correct chained transformation', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400" - }, { - "rt": "90" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400:rt-90/test_path.jpg`); - }); - - - it('should generate the correct chained transformation url with new undocumented tranforamtion parameter', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400" - }, { - "rndm_trnsf": "abcd" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400:rndm_trnsf-abcd/test_path.jpg`); - }); - - it('Overlay image', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400", - "raw": "l-image,i-overlay.jpg,w-100,b-10_CDDC39,l-end" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,l-image,i-overlay.jpg,w-100,b-10_CDDC39,l-end/test_path.jpg`); - }); - - it('Overlay image with slash in path', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400", - "raw": "l-image,i-/path/to/overlay.jpg,w-100,b-10_CDDC39,l-end" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,l-image,i-/path/to/overlay.jpg,w-100,b-10_CDDC39,l-end/test_path.jpg`); - }); - - it('Border', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "height": "300", - "width": "400", - border: "20_FF0000" - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,b-20_FF0000/test_path.jpg`); - }); - - it('e-sharpen - ', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - "e-sharpen": "-", - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:e-sharpen/test_path.jpg`); - }); - - - it('transformation with defaultImage', function () { - const url = imagekit.url({ - path: "/test_path1.jpg", - transformation: [{ - defaultImage: "test_path.jpg", - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:di-test_path.jpg/test_path1.jpg`); - }); - - it('skip transformation if it is undefined or null', function () { - const url = imagekit.url({ - path: "/test_path1.jpg", - transformation: [{ - defaultImage: "/test_path.jpg", - quality: undefined, - effectContrast: null - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:di-test_path.jpg/test_path1.jpg`); - }); - - it("Signed URL with ' in filename", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/test_'_path_alt.jpg"; - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/test_'_path_alt.jpg", - signed: true, - }); - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/test_'_path_alt.jpg?ik-s=${signature}`); - }); - - it("Signed URL with ' in filename and path", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/a'b/test_'_path_alt.jpg"; - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/a'b/test_'_path_alt.jpg", - signed: true, - }); - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/a'b/test_'_path_alt.jpg?ik-s=${signature}`); - }); - - it("Signed URL with ' in filename, path and transformation as path", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Imagekit',fs-50,l-end/a'b/test_'_path_alt.jpg"; - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - - const url = imagekit.url({ - path: "/a'b/test_'_path_alt.jpg", - signed: true, - transformation: [{ raw: "l-text,i-Imagekit',fs-50,l-end" }], - transformationPosition: "path", - }); - expect(url).equal( - `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Imagekit',fs-50,l-end/a'b/test_'_path_alt.jpg?ik-s=${signature}` - ); - }); - - it("Signed URL with ' in filename, path and transformation as query", function () { - const testURL = "https://ik.imagekit.io/test_url_endpoint/a'b/test_'_path_alt.jpg?tr=l-text%2Ci-Imagekit%27%2Cfs-50%2Cl-end"; - const signature = getSignature({ - privateKey: "test_private_key", - url: testURL, - urlEndpoint: "https://ik.imagekit.io/test_url_endpoint", - expiryTimestamp: "9999999999", - }); - const url = imagekit.url({ - path: "/a'b/test_'_path_alt.jpg", - signed: true, - transformation: [{ raw: "l-text,i-Imagekit',fs-50,l-end" }], - transformationPosition: "query", - }); - expect(url).equal( - `https://ik.imagekit.io/test_url_endpoint/a'b/test_'_path_alt.jpg?tr=l-text%2Ci-Imagekit%27%2Cfs-50%2Cl-end&ik-s=${signature}` - ); - }); - - - it('All combined', function () { - const url = imagekit.url({ - path: "/test_path.jpg", - transformation: [{ - height: 300, - width: 400, - aspectRatio: '4-3', - quality: 40, - crop: 'force', - cropMode: 'extract', - focus: 'left', - format: 'jpeg', - radius: 50, - bg: "A94D34", - border: "5-A94D34", - rotation: 90, - blur: 10, - named: "some_name", - progressive: true, - lossless: true, - trim: 5, - metadata: true, - colorProfile: true, - defaultImage: "/folder/file.jpg/", //trailing and leading slash case - dpr: 3, - effectSharpen: 10, - effectUSM: "2-2-0.8-0.024", - effectContrast: true, - effectGray: true, - original: true, - effectShadow: 'bl-15_st-40_x-10_y-N5', - effectGradient: 'from-red_to-white', - raw: "h-200,w-300,l-image,i-logo.png,l-end", - }] - }) - - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,e-sharpen-10,e-usm-2-2-0.8-0.024,e-contrast-true,e-grayscale-true,orig-true,e-shadow-bl-15_st-40_x-10_y-N5,e-gradient-from-red_to-white,h-200,w-300,l-image,i-logo.png,l-end/test_path.jpg`); - }); -}); - - diff --git a/tests/webhook-signature.js b/tests/webhook-signature.js deleted file mode 100644 index 4e3dd4a5..00000000 --- a/tests/webhook-signature.js +++ /dev/null @@ -1,145 +0,0 @@ -import ImageKit from "../index"; -import { expect } from "chai"; - -// Sample webhook data -const WEBHOOK_REQUEST_SAMPLE_SECRET = "whsec_xeO2UNkfKMQnfJf7Q/Qx+fYptL1wabXd"; -const WEBHOOK_REQUEST_SAMPLE_TIMESTAMP = new Date(1655788406333); -const WEBHOOK_REQUEST_SAMPLE_SIGNATURE_HEADER = - "t=1655788406333,v1=d30758f47fcb31e1fa0109d3b3e2a6c623e699aaf1461cba6bd462ef58ea4b31"; -const WEBHOOK_REQUEST_SAMPLE_RAW_BODY = - '{"type":"video.transformation.accepted","id":"58e6d24d-6098-4319-be8d-40c3cb0a402d","created_at":"2022-06-20T11:59:58.461Z","request":{"x_request_id":"fa98fa2e-d6cd-45b4-acf5-bc1d2bbb8ba9","url":"http://ik.imagekit.io/demo/sample-video.mp4?tr=f-webm,q-10","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:101.0) Gecko/20100101 Firefox/101.0"},"data":{"asset":{"url":"http://ik.imagekit.io/demo/sample-video.mp4"},"transformation":{"type":"video-transformation","options":{"video_codec":"vp9","audio_codec":"opus","auto_rotate":true,"quality":10,"format":"webm"}}}}'; -const WEBHOOK_REQUEST_SAMPLE = Object.seal({ - secret: WEBHOOK_REQUEST_SAMPLE_SECRET, - timestamp: WEBHOOK_REQUEST_SAMPLE_TIMESTAMP, - signatureHeader: WEBHOOK_REQUEST_SAMPLE_SIGNATURE_HEADER, - rawBody: WEBHOOK_REQUEST_SAMPLE_RAW_BODY, - body: JSON.parse(WEBHOOK_REQUEST_SAMPLE_RAW_BODY), -}); - -describe("WebhookSignature", function () { - const verify = (new ImageKit(require("./data").initializationParams)).verifyWebhookEvent; - - context("Test Webhook.verify() - Positive cases", () => { - it("Verify with body as string", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const { timestamp, event } = verify( - webhookRequest.rawBody, - webhookRequest.signatureHeader, - webhookRequest.secret - ); - expect(timestamp).to.equal(webhookRequest.timestamp.getTime()); - expect(event).to.deep.equal(webhookRequest.body); - }); - it("Verify with body as Buffer", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const { timestamp, event } = verify( - Buffer.from(webhookRequest.rawBody), - webhookRequest.signatureHeader, - webhookRequest.secret - ); - expect(timestamp).to.equal(webhookRequest.timestamp.getTime()); - expect(event).to.deep.equal(webhookRequest.body); - }); - it("Verify with body as Uint8Array", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const rawBody = Uint8Array.from(Buffer.from(webhookRequest.rawBody)); - const { timestamp, event } = verify( - rawBody, - webhookRequest.signatureHeader, - webhookRequest.secret - ); - expect(timestamp).to.equal(webhookRequest.timestamp.getTime()); - expect(event).to.deep.equal(webhookRequest.body); - }); - }); - - context("Test WebhookSignature.verify() - Negative cases", () => { - it("Timestamp missing", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const invalidSignature = - "v1=b6bc2aa82491c32f1cbef0eb52b7ffffff467ea65a03b5d4ccdcfb9e0941c946"; - try { - verify(webhookRequest.rawBody, invalidSignature, webhookRequest.secret); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Timestamp missing"); - } - }); - it("Timestamp invalid", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const invalidSignature = - "t=notANumber,v1=b6bc2aa82491c32f1cbef0eb52b7ffffff467ea65a03b5d4ccdcfb9e0941c946"; - try { - verify(webhookRequest.rawBody, invalidSignature, webhookRequest.secret); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Timestamp invalid"); - } - }); - it("Signature missing", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const invalidSignature = "t=1656326161409"; - try { - verify(webhookRequest.rawBody, invalidSignature, webhookRequest.secret); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Signature missing"); - } - }); - it("Incorrect signature - v1 manipulated", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const invalidSignature = `t=${webhookRequest.timestamp.getTime()},v1=d66b01d8f1e158d1af7646184716037510ac8ce0a1e70b726a1b698f954785b2`; - try { - verify(webhookRequest.rawBody, invalidSignature, webhookRequest.secret); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Incorrect signature"); - } - }); - it("Incorrect signature - incorrect request body", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const incorrectBody = { hello: "world" }; - const incorrectRawBody = JSON.stringify(incorrectBody); - try { - verify( - incorrectRawBody, - webhookRequest.signatureHeader, - webhookRequest.secret - ); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Incorrect signature"); - } - }); - it("Incorrect signature - timestamp manipulated", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - const incorrectSignature = webhookRequest.signatureHeader.replace( - `t=${webhookRequest.timestamp.getTime()}`, - `t=${webhookRequest.timestamp.getTime() + 1}` - ); // Correct timestamp replaced with incorrect timestamp - try { - verify( - webhookRequest.rawBody, - incorrectSignature, - webhookRequest.secret - ); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Incorrect signature"); - } - }); - it("Incorrect signature - different secret", () => { - const webhookRequest = WEBHOOK_REQUEST_SAMPLE; - try { - verify( - webhookRequest.rawBody, - webhookRequest.signatureHeader, - "A different secret" - ); - expect.fail("Expected exception"); - } catch (e) { - expect(e.message).to.equal("Incorrect signature"); - } - }); - }); -}); diff --git a/tsc-multi.json b/tsc-multi.json new file mode 100644 index 00000000..384ddac5 --- /dev/null +++ b/tsc-multi.json @@ -0,0 +1,15 @@ +{ + "targets": [ + { + "extname": ".js", + "module": "commonjs", + "shareHelpers": "internal/tslib.js" + }, + { + "extname": ".mjs", + "module": "esnext", + "shareHelpers": "internal/tslib.mjs" + } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..3d7881a2 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "@imagekit/nodejs/*": ["dist/src/*"], + "@imagekit/nodejs": ["dist/src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/tsconfig.deno.json b/tsconfig.deno.json new file mode 100644 index 00000000..849e070d --- /dev/null +++ b/tsconfig.deno.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist-deno"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist-deno", + "lib": ["es2020", "DOM"], + "noEmit": true, + "declaration": true, + "declarationMap": true, + "outDir": "dist-deno", + "pretty": true, + "sourceMap": true + } +} diff --git a/tsconfig.dist-src.json b/tsconfig.dist-src.json new file mode 100644 index 00000000..c550e299 --- /dev/null +++ b/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "ES2015", + "lib": ["DOM", "DOM.Iterable", "ES2018"], + "moduleResolution": "node" + } +} diff --git a/tsconfig.json b/tsconfig.json index 69e4ceab..0136ffb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,74 +1,38 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ - - /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - "typeRoots": ["./types"], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ - "resolveJsonModule": true - }, - "include": ["index.ts", "libs/**/*", "utils/*", "test/*", "types/*"], - "exclude": ["node_modules", "dist"] - } \ No newline at end of file + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "@imagekit/nodejs/*": ["src/*"], + "@imagekit/nodejs": ["src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "isolatedModules": false, + + "skipLibCheck": true + } +} diff --git a/utils/authorization.ts b/utils/authorization.ts deleted file mode 100644 index 231c1672..00000000 --- a/utils/authorization.ts +++ /dev/null @@ -1,16 +0,0 @@ -import FormData from "form-data"; - -interface RequestOptions { - url: string; - headers?: Record; - method: string; - formData?: FormData; - qs?: Object; - json?: any; - auth?: { - user: string; - pass: string; - }; -} - -export type { RequestOptions }; diff --git a/utils/hamming-distance.d.ts b/utils/hamming-distance.d.ts deleted file mode 100644 index 7fa5d2c3..00000000 --- a/utils/hamming-distance.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "hamming-distance"; diff --git a/utils/phash.ts b/utils/phash.ts deleted file mode 100644 index dc2f3d8d..00000000 --- a/utils/phash.ts +++ /dev/null @@ -1,30 +0,0 @@ -// import packages -import compare from "hamming-distance"; -// import constants -import errors from "../libs/constants/errorMessages"; - -// regexp validator -const hexRegExp = new RegExp(/^[0-9a-fA-F]+$/, "i"); - -const errorHandler = (error: { message: string; help: string }): Error => new Error(`${error.message}: ${error.help}`); - -const pHashDistance = (firstHash: string, secondHash: string): number | Error => { - if (!firstHash || !secondHash) { - return errorHandler(errors.MISSING_PHASH_VALUE); - } - if (!hexRegExp.test(firstHash) || !hexRegExp.test(secondHash)) { - return errorHandler(errors.INVALID_PHASH_VALUE); - } - - const firstHashString = firstHash.toString(); - const secondHashString = secondHash.toString(); - - if (firstHashString.length !== secondHashString.length) { - return errorHandler(errors.UNEQUAL_STRING_LENGTH); - } - - const distance = compare(firstHashString, secondHashString); - return distance; -}; - -export default { pHashDistance }; diff --git a/utils/request.ts b/utils/request.ts deleted file mode 100644 index aa1bf855..00000000 --- a/utils/request.ts +++ /dev/null @@ -1,91 +0,0 @@ -import respond from "./respond"; -import { RequestOptions } from "./authorization"; -import { ImageKitOptions } from "../libs/interfaces"; -import { IKCallback } from "../libs/interfaces/IKCallback"; -import axios, { AxiosError, AxiosHeaders, AxiosRequestConfig, AxiosResponse } from "axios"; - -// constant -const UnknownError: string = "Unknown error occured"; - -export default function request( - requestOptions: RequestOptions, - defaultOptions: ImageKitOptions, - callback?: IKCallback, -) { - - var options: AxiosRequestConfig = { - method: requestOptions.method, - url: requestOptions.url, - auth: { - username: defaultOptions.privateKey || "", - password: "", - }, - maxBodyLength: Infinity, - }; - - if (typeof requestOptions.json === "object") options.data = requestOptions.json; - else if (typeof requestOptions.formData === "object") options.data = requestOptions.formData; - - if (typeof requestOptions.qs === "object") options.params = requestOptions.qs; - if (typeof requestOptions.headers === "object") options.headers = requestOptions.headers; - - axios(options).then((response: AxiosResponse) => { - if (typeof callback !== "function") return; - const { data, status, headers } = response; - const responseMetadata = { - statusCode: status, - headers: (headers as AxiosHeaders).toJSON() - } - let result = data ? data : {} as T; - // define status code and headers as non-enumerable properties on data - Object.defineProperty(result, "$ResponseMetadata", { - value: responseMetadata, - enumerable: false, - writable: false - }); - respond(false, result, callback); - }, (error: AxiosError) => { - if (typeof callback !== "function") return; - if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - const responseMetadata = { - statusCode: error.response.status, - headers: (error.response.headers as AxiosHeaders).toJSON() - } - - let result = {} as Object; - if (error.response.data && typeof error.response.data === "object") { - result = error.response.data - } else if (error.response.data && typeof error.response.data === "string") { - result = { - help: error.response.data - } - } - - if (error.response.status === 429) { - result = { - ...result, - "X-RateLimit-Limit": parseInt(error.response.headers["x-ratelimit-limit"], 10), - "X-RateLimit-Reset": parseInt(error.response.headers["x-ratelimit-reset"], 10), - "X-RateLimit-Interval": parseInt(error.response.headers["x-ratelimit-interval"], 10), - } - } - // define status code and headers as non-enumerable properties on data - Object.defineProperty(result, "$ResponseMetadata", { - value: responseMetadata, - enumerable: false, - writable: false - }); - respond(true, result, callback); - - } else if (error) { - respond(true, error, callback); - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js - } else { - respond(true, new Error(UnknownError), callback); - } - }) -} diff --git a/utils/respond.ts b/utils/respond.ts deleted file mode 100644 index 96c359e3..00000000 --- a/utils/respond.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IKCallback } from "../libs/interfaces/IKCallback"; - -export default function respond(isError: boolean, response: any, callback?: IKCallback) { - if (typeof callback === "function") { - if (isError) { - callback(response, null); - } else { - callback(null, response); - } - } -} diff --git a/utils/transformation.ts b/utils/transformation.ts deleted file mode 100644 index 5b89e51a..00000000 --- a/utils/transformation.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - VARIABLES -*/ -import supportedTransforms from "../libs/constants/supportedTransforms"; -import { UrlOptions, TransformationPosition } from "../libs/interfaces"; - -const DEFAULT_TRANSFORMATION_POSITION: TransformationPosition = "path"; -const QUERY_TRANSFORMATION_POSITION: TransformationPosition = "query"; - -const CHAIN_TRANSFORM_DELIMITER: string = ":"; -const TRANSFORM_DELIMITER: string = ","; -const TRANSFORM_KEY_VALUE_DELIMITER: string = "-"; - -const getDefault = function (): TransformationPosition { - return DEFAULT_TRANSFORMATION_POSITION; -}; - -const addAsQueryParameter = function (options: UrlOptions): boolean { - return options.transformationPosition === QUERY_TRANSFORMATION_POSITION; -}; - -const getTransformKey = function (transform: string): string { - if (!transform) { - return ""; - } - - return supportedTransforms[transform] || supportedTransforms[transform.toLowerCase()] || ""; -}; - -const getChainTransformDelimiter = function (): string { - return CHAIN_TRANSFORM_DELIMITER; -}; - -const getTransformDelimiter = function (): string { - return TRANSFORM_DELIMITER; -}; - -const getTransformKeyValueDelimiter = function (): string { - return TRANSFORM_KEY_VALUE_DELIMITER; -}; - -export default { - getDefault, - addAsQueryParameter, - getTransformKey, - getChainTransformDelimiter, - getTransformDelimiter, - getTransformKeyValueDelimiter, -}; diff --git a/utils/urlFormatter.ts b/utils/urlFormatter.ts deleted file mode 100644 index 96e2b158..00000000 --- a/utils/urlFormatter.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Adds a leading slash to the given string if it does not already start with one. - * - * @param {string} str - The input string to be processed. - * @returns {string} - The modified string with a leading slash if it was missing. - */ -const addLeadingSlash = function (str: string) { - // Check if the input is a string and does not start with a slash - if (typeof str === "string" && str[0] !== "/") { - // Prepend a slash to the string - str = "/" + str; - } - - // Return the processed string - return str; -}; - - -/** - * Removes the leading slash from the given string if it starts with one. - * - * @param {string} str - The input string to be processed. - * @returns {string} - The modified string with the leading slash removed if it was present. - */ -const removeLeadingSlash = function (str: string) { - // Check if the input is a string and starts with a slash - if (typeof str === "string" && str[0] === "/") { - // Remove the leading slash from the string - str = str.substring(1); - } - - // Return the processed string - return str; -}; - - -/** - * Removes the trailing slash from the given string if it ends with one. - * - * @param {string} str - The input string to be processed. - * @returns {string} - The modified string with the trailing slash removed if it was present. - */ -const removeTrailingSlash = function (str: string) { - // Check if the input is a string and ends with a slash - if (typeof str === "string" && str[str.length - 1] === "/") { - // Remove the trailing slash from the string - str = str.substring(0, str.length - 1); - } - - // Return the processed string - return str; -}; - - -/** - * Adds a trailing slash to the given string if it does not already end with one. - * - * @param {string} str - The input string to be processed. - * @returns {string} - The modified string with a trailing slash if it was missing. - */ -const addTrailingSlash = function (str: string) { - // Check if the input is a string and does not end with a slash - if (typeof str === "string" && str[str.length - 1] !== "/") { - // Append a trailing slash to the string - str = str + "/"; - } - - // Return the processed string - return str; -}; - - -export default { addLeadingSlash, removeLeadingSlash, removeTrailingSlash, addTrailingSlash }; diff --git a/utils/webhook-signature.ts b/utils/webhook-signature.ts deleted file mode 100644 index 24028825..00000000 --- a/utils/webhook-signature.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { createHmac } from "crypto"; -import { isNaN } from "lodash"; -import errorMessages from "../libs/constants/errorMessages"; -import type { WebhookEvent } from "../libs/interfaces"; - -/** - * @description Enum for Webhook signature item names - */ -enum SignatureItems { - Timestamp = "t", - V1 = "v1", -} - -const HASH_ALGORITHM = "sha256"; - -/** - * @param timstamp - Webhook request timestamp - * @param payload - Webhook payload as UTF8 encoded string - * @param secret - Webhook secret as UTF8 encoded string - * @returns Hmac with webhook secret as key and `${timestamp}.${payload}` as hash payload. - */ -const computeHmac = ( - timstamp: Date, - payload: string, - secret: string -): string => { - const hashPayload = `${timstamp.getTime()}.${payload}`; - return createHmac(HASH_ALGORITHM, secret).update(hashPayload).digest("hex"); -}; - -/** - * @description Extract items from webhook signature string - */ -const deserializeSignature = ( - signature: string -): { - timestamp: number; - v1: string; -} => { - const items = signature.split(","); - const itemMap = items.map((item) => item.split("=")); // eg. [["t", 1656921250765], ["v1", 'afafafafafaf']] - const timestampString = itemMap.find( - ([key]) => key === SignatureItems.Timestamp - )?.[1]; // eg. 1656921250765 - - // parse timestamp - if (timestampString === undefined) { - throw new Error( - errorMessages.VERIFY_WEBHOOK_EVENT_TIMESTAMP_MISSING.message - ); - } - const timestamp = parseInt(timestampString, 10); - if (isNaN(timestamp) || timestamp < 0) { - throw new Error( - errorMessages.VERIFY_WEBHOOK_EVENT_TIMESTAMP_INVALID.message - ); - } - - // parse v1 signature - const v1 = itemMap.find(([key]) => key === SignatureItems.V1)?.[1]; // eg. 'afafafafafaf' - if (v1 === undefined) { - throw new Error( - errorMessages.VERIFY_WEBHOOK_EVENT_SIGNATURE_MISSING.message - ); - } - - return { timestamp, v1 }; -}; - -/** - * @param payload - Raw webhook request body (Encoded as UTF8 string or Buffer) - * @param signature - Webhook signature as UTF8 encoded strings (Stored in `x-ik-signature` header of the request) - * @param secret - Webhook secret as UTF8 encoded string [Copy from ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks) - * @returns \{ `timstamp`: Verified UNIX epoch timestamp if signature, `event`: Parsed webhook event payload \} - */ -export const verify = ( - payload: string | Uint8Array, - signature: string, - secret: string -): { - timestamp: number; - event: WebhookEvent; -} => { - const { timestamp, v1 } = deserializeSignature(signature); - const payloadAsString: string = - typeof payload === "string" - ? payload - : Buffer.from(payload).toString("utf8"); - const computedHmac = computeHmac( - new Date(timestamp), - payloadAsString, - secret - ); - if (v1 !== computedHmac) { - throw new Error( - errorMessages.VERIFY_WEBHOOK_EVENT_SIGNATURE_INCORRECT.message - ); - } - return { - timestamp, - event: JSON.parse(payloadAsString) as WebhookEvent, - }; -}; diff --git a/yarn.lock b/yarn.lock index f033c13b..1935915b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,438 +2,199 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: - "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/cli@^7.14.5": - version "7.17.10" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.17.10.tgz#5ea0bf6298bb78f3b59c7c06954f9bd1c79d5943" - integrity sha512-OygVO1M2J4yPMNOW9pb+I6kFGpQK77HmG44Oz3hg8xQIl5L/2zq+ZohwAdSaqYgVwM0SfmPHZHphH4wR8qzVYw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.8" - commander "^4.0.1" - convert-source-map "^1.1.0" - fs-readdir-recursive "^1.1.0" - glob "^7.0.0" - make-dir "^2.1.0" - slash "^2.0.0" - optionalDependencies: - "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" - -"@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== - dependencies: - "@babel/highlight" "^7.16.7" - -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.10": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" - integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== - -"@babel/core@^7.14.6", "@babel/core@^7.7.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" - integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helpers" "^7.18.2" - "@babel/parser" "^7.18.5" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.5" - "@babel/types" "^7.18.4" - convert-source-map "^1.7.0" +"@andrewbranch/untar.js@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" + integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== + +"@arethetypeswrong/cli@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.0.tgz#f97f10926b3f9f9eb5117550242d2e06c25cadac" + integrity sha512-xSMW7bfzVWpYw5JFgZqBXqr6PdR0/REmn3DkxCES5N0JTcB0CVgbIynJCvKBFmXaPc3hzmmTrb7+yPDRoOSZdA== + dependencies: + "@arethetypeswrong/core" "0.17.0" + chalk "^4.1.2" + cli-table3 "^0.6.3" + commander "^10.0.1" + marked "^9.1.2" + marked-terminal "^7.1.0" + semver "^7.5.4" + +"@arethetypeswrong/core@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.0.tgz#abb3b5f425056d37193644c2a2de4aecf866b76b" + integrity sha512-FHyhFizXNetigTVsIhqXKGYLpazPS5YNojEPpZEUcBPt9wVvoEbNIvG+hybuBR+pjlRcbyuqhukHZm1fr+bDgA== + dependencies: + "@andrewbranch/untar.js" "^1.0.3" + cjs-module-lexer "^1.2.3" + fflate "^0.8.2" + lru-cache "^10.4.3" + semver "^7.5.4" + typescript "5.6.1-rc" + validate-npm-package-name "^5.0.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" + integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.6" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/generator@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" - integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== +"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== dependencies: - "@babel/types" "^7.18.2" - "@jridgewell/gen-mapping" "^0.3.0" + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" - integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" - integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10", "@babel/helper-compilation-targets@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" - integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.20.2" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.17.12", "@babel/helper-create-class-features-plugin@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19" - integrity sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-member-expression-to-functions" "^7.17.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - -"@babel/helper-create-regexp-features-plugin@^7.16.7", "@babel/helper-create-regexp-features-plugin@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz#bb37ca467f9694bbe55b884ae7a5cc1e0084e4fd" - integrity sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - regexpu-core "^5.0.1" - -"@babel/helper-define-polyfill-provider@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" - integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== - dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" - integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== - -"@babel/helper-explode-assignable-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" - integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" - integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" - integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-member-expression-to-functions@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" - integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" - integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-transforms@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" - integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.0" - "@babel/types" "^7.18.0" - -"@babel/helper-optimise-call-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" - integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" - integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== - -"@babel/helper-remap-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" - integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-wrap-function" "^7.16.8" - "@babel/types" "^7.16.8" - -"@babel/helper-replace-supers@^7.16.7", "@babel/helper-replace-supers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz#41fdfcc9abaf900e18ba6e5931816d9062a7b2e0" - integrity sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q== - dependencies: - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-member-expression-to-functions" "^7.17.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" - -"@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" - integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== - dependencies: - "@babel/types" "^7.18.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" - integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== - dependencies: - "@babel/types" "^7.16.0" - -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" - integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== - -"@babel/helper-wrap-function@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" - integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== - dependencies: - "@babel/helper-function-name" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.16.8" - "@babel/types" "^7.16.8" - -"@babel/helpers@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" - integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.18.2" - "@babel/types" "^7.18.2" - -"@babel/highlight@^7.16.7": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" - integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" + integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.6" + "@babel/types" "^7.23.6" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/node@^7.14.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.18.5.tgz#b44a790b8896436908ebcaf4816c1efce73d61cb" - integrity sha512-zv94ESipS2/YKAOJ+/WAfVEzsl9M8UmPZ7Hwx5qVPgytdrgwUPxfi700iR9KO/w5ZhIHyFyvoZtCTSEcQJF8vQ== - dependencies: - "@babel/register" "^7.17.7" - commander "^4.0.1" - core-js "^3.22.1" - node-environment-flags "^1.0.5" - regenerator-runtime "^0.13.4" - v8flags "^3.1.1" - -"@babel/parser@^7.16.7", "@babel/parser@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" - integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz#1dca338caaefca368639c9ffb095afbd4d420b1e" - integrity sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz#0d498ec8f0374b1e2eb54b9cb2c4c78714c77753" - integrity sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.17.12" - -"@babel/plugin-proposal-async-generator-functions@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz#094a417e31ce7e692d84bab06c8e2a607cbeef03" - integrity sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-remap-async-to-generator" "^7.16.8" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz#84f65c0cc247d46f40a6da99aadd6438315d80a4" - integrity sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-proposal-class-static-block@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz#7d02253156e3c3793bdb9f2faac3a1c05f0ba710" - integrity sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-dynamic-import@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" - integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz#b22864ccd662db9606edb2287ea5fd1709f05378" - integrity sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz#f4642951792437233216d8c1af370bb0fbff4664" - integrity sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz#c64a1bcb2b0a6d0ed2ff674fd120f90ee4b88a23" - integrity sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz#1e93079bbc2cbc756f6db6a1925157c4a92b94be" - integrity sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" - integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz#79f2390c892ba2a68ec112eb0d895cfbd11155e8" - integrity sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-compilation-targets" "^7.17.10" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.17.12" - -"@babel/plugin-proposal-optional-catch-binding@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" - integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz#f96949e9bacace3a9066323a5cf90cfb9de67174" - integrity sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz#c2ca3a80beb7539289938da005ad525a038a819c" - integrity sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-proposal-private-property-in-object@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz#b02efb7f106d544667d91ae97405a9fd8c93952d" - integrity sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-create-class-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.17.12", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz#3dbd7a67bd7f94c8238b394da112d86aaf32ad4d" - integrity sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -442,40 +203,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": +"@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-syntax-import-assertions@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz#58096a92b11b2e4e54b24c6a0cc0e5e607abcedd" - integrity sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw== +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" @@ -484,7 +231,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== @@ -498,7 +252,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.4": +"@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== @@ -526,607 +280,792 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": +"@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" - integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" + integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/template@^7.22.15", "@babel/template@^7.3.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" + integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" -"@babel/plugin-transform-arrow-functions@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz#dddd783b473b1b1537ef46423e3944ff24898c45" - integrity sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" -"@babel/plugin-transform-async-to-generator@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz#dbe5511e6b01eee1496c944e35cdfe3f58050832" - integrity sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-remap-async-to-generator" "^7.16.8" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@babel/plugin-transform-block-scoped-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" - integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@babel/plugin-transform-block-scoping@^7.17.12": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz#7988627b3e9186a13e4d7735dc9c34a056613fb9" - integrity sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== -"@babel/plugin-transform-classes@^7.17.12": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz#51310b812a090b846c784e47087fa6457baef814" - integrity sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A== +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-replace-supers" "^7.18.2" - "@babel/helper-split-export-declaration" "^7.16.7" - globals "^11.1.0" + "@cspotcode/source-map-consumer" "0.8.0" -"@babel/plugin-transform-computed-properties@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz#bca616a83679698f3258e892ed422546e531387f" - integrity sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ== +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + eslint-visitor-keys "^3.3.0" -"@babel/plugin-transform-destructuring@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz#dc4f92587e291b4daa78aa20cc2d7a63aa11e858" - integrity sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" - integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" -"@babel/plugin-transform-duplicate-keys@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz#a09aa709a3310013f8e48e0e23bc7ace0f21477c" - integrity sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw== +"@eslint/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" + integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@types/json-schema" "^7.0.15" -"@babel/plugin-transform-exponentiation-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" - integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@types/json-schema" "^7.0.15" -"@babel/plugin-transform-for-of@^7.18.1": - version "7.18.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz#ed14b657e162b72afbbb2b4cdad277bf2bb32036" - integrity sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" + integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== + dependencies: + "@eslint/core" "^0.10.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@babel/plugin-transform-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" - integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== - dependencies: - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@babel/plugin-transform-literals@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz#97131fbc6bbb261487105b4b3edbf9ebf9c830ae" - integrity sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== -"@babel/plugin-transform-member-expression-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" - integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@babel/plugin-transform-modules-amd@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz#7ef1002e67e36da3155edc8bf1ac9398064c02ed" - integrity sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - babel-plugin-dynamic-import-node "^2.3.3" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" -"@babel/plugin-transform-modules-commonjs@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e" - integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ== +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== dependencies: - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-simple-access" "^7.18.2" - babel-plugin-dynamic-import-node "^2.3.3" + "@jest/types" "^29.6.3" -"@babel/plugin-transform-modules-systemjs@^7.18.0": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.5.tgz#87f11c44fbfd3657be000d4897e192d9cb535996" - integrity sha512-SEewrhPpcqMF1V7DhnEbhVJLrC+nnYfe1E0piZMZXBpxi9WvZqWGwpsk7JYP7wPWeqaBh4gyKlBhHJu3uz5g4Q== +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-validator-identifier" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" -"@babel/plugin-transform-modules-umd@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz#56aac64a2c2a1922341129a4597d1fd5c3ff020f" - integrity sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA== +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: - "@babel/helper-module-transforms" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" + jest-get-type "^29.6.3" -"@babel/plugin-transform-named-capturing-groups-regex@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz#9c4a5a5966e0434d515f2675c227fd8cc8606931" - integrity sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA== +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.17.12" - "@babel/helper-plugin-utils" "^7.17.12" + expect "^29.7.0" + jest-snapshot "^29.7.0" -"@babel/plugin-transform-new-target@^7.17.12": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.5.tgz#8c228c4a07501dd12c95c5f23d1622131cc23931" - integrity sha512-TuRL5uGW4KXU6OsRj+mLp9BM7pO8e7SGNTEokQRRxHFkXYMFiy2jlKSZPFtI/mKORDzciH+hneskcSOp0gU8hg== +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== dependencies: - "@babel/helper-plugin-utils" "^7.17.12" + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" -"@babel/plugin-transform-object-super@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" - integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" -"@babel/plugin-transform-parameters@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz#eb467cd9586ff5ff115a9880d6fdbd4a846b7766" - integrity sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-transform-property-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" - integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-regenerator@^7.18.0": - version "7.18.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz#44274d655eb3f1af3f3a574ba819d3f48caf99d5" - integrity sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - regenerator-transform "^0.15.0" - -"@babel/plugin-transform-reserved-words@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz#7dbd349f3cdffba751e817cf40ca1386732f652f" - integrity sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-transform-shorthand-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" - integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-spread@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz#c112cad3064299f03ea32afed1d659223935d1f5" - integrity sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - -"@babel/plugin-transform-sticky-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" - integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-template-literals@^7.18.2": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz#31ed6915721864847c48b656281d0098ea1add28" - integrity sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-transform-typeof-symbol@^7.17.12": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz#0f12f57ac35e98b35b4ed34829948d42bd0e6889" - integrity sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - -"@babel/plugin-transform-typescript@^7.17.12": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz#587eaf6a39edb8c06215e550dc939faeadd750bf" - integrity sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.0" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/plugin-syntax-typescript" "^7.17.12" - -"@babel/plugin-transform-unicode-escapes@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" - integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-unicode-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" - integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/preset-env@^7.14.5": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.2.tgz#f47d3000a098617926e674c945d95a28cb90977a" - integrity sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q== - dependencies: - "@babel/compat-data" "^7.17.10" - "@babel/helper-compilation-targets" "^7.18.2" - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.17.12" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.17.12" - "@babel/plugin-proposal-async-generator-functions" "^7.17.12" - "@babel/plugin-proposal-class-properties" "^7.17.12" - "@babel/plugin-proposal-class-static-block" "^7.18.0" - "@babel/plugin-proposal-dynamic-import" "^7.16.7" - "@babel/plugin-proposal-export-namespace-from" "^7.17.12" - "@babel/plugin-proposal-json-strings" "^7.17.12" - "@babel/plugin-proposal-logical-assignment-operators" "^7.17.12" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.17.12" - "@babel/plugin-proposal-numeric-separator" "^7.16.7" - "@babel/plugin-proposal-object-rest-spread" "^7.18.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" - "@babel/plugin-proposal-optional-chaining" "^7.17.12" - "@babel/plugin-proposal-private-methods" "^7.17.12" - "@babel/plugin-proposal-private-property-in-object" "^7.17.12" - "@babel/plugin-proposal-unicode-property-regex" "^7.17.12" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.17.12" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.17.12" - "@babel/plugin-transform-async-to-generator" "^7.17.12" - "@babel/plugin-transform-block-scoped-functions" "^7.16.7" - "@babel/plugin-transform-block-scoping" "^7.17.12" - "@babel/plugin-transform-classes" "^7.17.12" - "@babel/plugin-transform-computed-properties" "^7.17.12" - "@babel/plugin-transform-destructuring" "^7.18.0" - "@babel/plugin-transform-dotall-regex" "^7.16.7" - "@babel/plugin-transform-duplicate-keys" "^7.17.12" - "@babel/plugin-transform-exponentiation-operator" "^7.16.7" - "@babel/plugin-transform-for-of" "^7.18.1" - "@babel/plugin-transform-function-name" "^7.16.7" - "@babel/plugin-transform-literals" "^7.17.12" - "@babel/plugin-transform-member-expression-literals" "^7.16.7" - "@babel/plugin-transform-modules-amd" "^7.18.0" - "@babel/plugin-transform-modules-commonjs" "^7.18.2" - "@babel/plugin-transform-modules-systemjs" "^7.18.0" - "@babel/plugin-transform-modules-umd" "^7.18.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.17.12" - "@babel/plugin-transform-new-target" "^7.17.12" - "@babel/plugin-transform-object-super" "^7.16.7" - "@babel/plugin-transform-parameters" "^7.17.12" - "@babel/plugin-transform-property-literals" "^7.16.7" - "@babel/plugin-transform-regenerator" "^7.18.0" - "@babel/plugin-transform-reserved-words" "^7.17.12" - "@babel/plugin-transform-shorthand-properties" "^7.16.7" - "@babel/plugin-transform-spread" "^7.17.12" - "@babel/plugin-transform-sticky-regex" "^7.16.7" - "@babel/plugin-transform-template-literals" "^7.18.2" - "@babel/plugin-transform-typeof-symbol" "^7.17.12" - "@babel/plugin-transform-unicode-escapes" "^7.16.7" - "@babel/plugin-transform-unicode-regex" "^7.16.7" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.18.2" - babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.5.0" - babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.22.1" - semver "^6.3.0" +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@babel/preset-typescript@^7.14.5": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz#40269e0a0084d56fc5731b6c40febe1c9a4a3e8c" - integrity sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg== - dependencies: - "@babel/helper-plugin-utils" "^7.17.12" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-typescript" "^7.17.12" - -"@babel/register@^7.14.5", "@babel/register@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.17.7.tgz#5eef3e0f4afc07e25e847720e7b987ae33f08d0b" - integrity sha512-fg56SwvXRifootQEDQAu1mKdjh5uthPzdO0N6t358FktfL4XjAVXuH58ULoiW8mesxiOgNIrxiImqEwv0+hRRA== - dependencies: - clone-deep "^4.0.1" - find-cache-dir "^2.0.0" - make-dir "^2.1.0" - pirates "^4.0.5" - source-map-support "^0.5.16" - -"@babel/runtime@^7.8.4": - version "7.18.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" - integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5": - version "7.18.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" - integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.18.2" - "@babel/helper-environment-visitor" "^7.18.2" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.18.5" - "@babel/types" "^7.18.4" - debug "^4.1.0" - globals "^11.1.0" +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.4.4": - version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" - integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" - integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" +"@pkgr/core@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.7" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" - integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@jridgewell/set-array@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" - integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.13" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" - integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" -"@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@sinonjs/commons" "^3.0.0" -"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": - version "2.1.8-no-fsevents.3" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" - integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== +"@stablelib/base64@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be" + integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ== + +"@swc/core-darwin-arm64@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" + integrity sha512-UOCcH1GvjRnnM/LWT6VCGpIk0OhHRq6v1U6QXuPt5wVsgXnXQwnf5k3sG5Cm56hQHDvhRPY6HCsHi/p0oek8oQ== + +"@swc/core-darwin-x64@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.16.tgz#a5bc7d8b1dd850adb0bb95c6b5c742b92201fd01" + integrity sha512-t3bgqFoYLWvyVtVL6KkFNCINEoOrIlyggT/kJRgi1y0aXSr0oVgcrQ4ezJpdeahZZ4N+Q6vT3ffM30yIunELNA== + +"@swc/core-linux-arm-gnueabihf@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.16.tgz#961744908ee5cbb79bc009dcf58cc8b831111f38" + integrity sha512-DvHuwvEF86YvSd0lwnzVcjOTZ0jcxewIbsN0vc/0fqm9qBdMMjr9ox6VCam1n3yYeRtj4VFgrjeNFksqbUejdQ== + +"@swc/core-linux-arm64-gnu@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.16.tgz#43713be3f26757d82d2745dc25f8b63400e0a3d0" + integrity sha512-9Uu5YlPbyCvbidjKtYEsPpyZlu16roOZ5c2tP1vHfnU9bgf5Tz5q5VovSduNxPHx+ed2iC1b1URODHvDzbbDuQ== + +"@swc/core-linux-arm64-musl@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.16.tgz#394a7d030f3a61902bd3947bb9d70d26d42f3c81" + integrity sha512-/YZq/qB1CHpeoL0eMzyqK5/tYZn/rzKoCYDviFU4uduSUIJsDJQuQA/skdqUzqbheOXKAd4mnJ1hT04RbJ8FPQ== + +"@swc/core-linux-x64-gnu@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.16.tgz#71eb108b784f9d551ee8a35ebcdaed972f567981" + integrity sha512-UUjaW5VTngZYDcA8yQlrFmqs1tLi1TxbKlnaJwoNhel9zRQ0yG1YEVGrzTvv4YApSuIiDK18t+Ip927bwucuVQ== + +"@swc/core-linux-x64-musl@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.16.tgz#10dbaedb4e3dfc7268e3a9a66ad3431471ef035b" + integrity sha512-aFhxPifevDTwEDKPi4eRYWzC0p/WYJeiFkkpNU5Uc7a7M5iMWPAbPFUbHesdlb9Jfqs5c07oyz86u+/HySBNPQ== + +"@swc/core-win32-arm64-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.16.tgz#80247adff6c245ff32b44d773c1a148858cd655f" + integrity sha512-bTD43MbhIHL2s5QgCwyleaGwl96Gk/scF2TaVKdUe4QlJCDV/YK9h5oIBAp63ckHtE8GHlH4c8dZNBiAXn4Org== + +"@swc/core-win32-ia32-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.16.tgz#e540afc3ccf3224267b4ddfb408f9d9737984686" + integrity sha512-/lmZeAN/qV5XbK2SEvi8e2RkIg8FQNYiSA8y2/Zb4gTUMKVO5JMLH0BSWMiIKMstKDPDSxMWgwJaQHF8UMyPmQ== + +"@swc/core-win32-x64-msvc@1.4.16": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.16.tgz#f880939fca32c181adfe7e3abd2b6b7857bd3489" + integrity sha512-BPAfFfODWXtUu6SwaTTftDHvcbDyWBSI/oanUeRbQR5vVWkXoQ3cxLTsDluc3H74IqXS5z1Uyoe0vNo2hB1opA== + +"@swc/core@^1.3.102": + version "1.4.16" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.16.tgz#d175bae2acfecd53bcbd4293f1fba5ec316634a0" + integrity sha512-Xaf+UBvW6JNuV131uvSNyMXHn+bh6LyKN4tbv7tOUFQpXyz/t9YWRE04emtlUW9Y0qrm/GKFCbY8n3z6BpZbTA== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "^0.1.5" + optionalDependencies: + "@swc/core-darwin-arm64" "1.4.16" + "@swc/core-darwin-x64" "1.4.16" + "@swc/core-linux-arm-gnueabihf" "1.4.16" + "@swc/core-linux-arm64-gnu" "1.4.16" + "@swc/core-linux-arm64-musl" "1.4.16" + "@swc/core-linux-x64-gnu" "1.4.16" + "@swc/core-linux-x64-musl" "1.4.16" + "@swc/core-win32-arm64-msvc" "1.4.16" + "@swc/core-win32-ia32-msvc" "1.4.16" + "@swc/core-win32-x64-msvc" "1.4.16" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== +"@swc/jest@^0.2.29": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== dependencies: - type-detect "4.0.8" + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" -"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== +"@swc/types@^0.1.5": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" + integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== dependencies: - "@sinonjs/commons" "^1.7.0" + "@swc/counter" "^0.1.3" -"@sinonjs/samsam@^5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" - integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== - dependencies: - "@sinonjs/commons" "^1.6.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== -"@sinonjs/text-encoding@^0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" - integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== -"@types/caseless@*": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" - integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== -"@types/chai@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" - integrity sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ== +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" -"@types/lodash@^4.14.170": - version "4.14.182" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" - integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" -"@types/mocha@^9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" - integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" -"@types/node@*": - version "18.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" - integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.4" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" + integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== + dependencies: + "@babel/types" "^7.20.7" -"@types/node@^15.12.2": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/request@^2.48.5": - version "2.48.8" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c" - integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ== +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== dependencies: - "@types/caseless" "*" "@types/node" "*" - "@types/tough-cookie" "*" - form-data "^2.5.0" -"@types/sinon@^10.0.12": - version "10.0.12" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.12.tgz#fb7009ea71f313a9da4644ba73b94e44d6b84f7f" - integrity sha512-uWf4QJ4oky/GckJ1MYQxU52cgVDcXwBhDkpvLbi4EKoLPqLE4MOH6T/ttM33l3hi0oZ882G6oIzWv/oupRYSxQ== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== dependencies: - "@types/sinonjs__fake-timers" "*" + "@types/istanbul-lib-coverage" "*" -"@types/sinonjs__fake-timers@*": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" - integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" -"@types/tough-cookie@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" - integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/jest@^29.4.0": + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" -"@types/uuid@^8.3.4": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@ungap/promise-all-settled@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" - integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@types/node@*": + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== + dependencies: + undici-types "~5.26.4" -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +"@types/node@^20.17.6": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== dependencies: - debug "4" + undici-types "~6.21.0" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== + dependencies: + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== + dependencies: + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== + +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== + dependencies: + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +acorn@^8.4.1: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== aggregate-error@^3.0.0: version "3.1.0" @@ -1136,21 +1075,40 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" -ansi-regex@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" - integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1165,25 +1123,28 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@~3.1.1, anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -append-transform@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" - integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== - dependencies: - default-require-extensions "^3.0.0" - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: version "1.0.10" @@ -1197,87 +1158,71 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -argv@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" - integrity sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw== - -array.prototype.reduce@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" - integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" - -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^1.6.5: - version "1.6.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.6.tgz#878db45401d91fe9e53aed8ac962ed93bde8dd1c" - integrity sha512-XZLZDFfXKM9U/Y/B4nNynfCRUqNyVZ4sBC/n9GDRCkq9vd2mIvKjKKsbIh1WPmHmNbg6ND7cTBY3Y2+u1G3/2Q== +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== dependencies: - follow-redirects "^1.15.4" - form-data "^4.0.0" - proxy-from-env "^1.1.0" + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: - object.assign "^4.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" -babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" - integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: - "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.1" - semver "^6.1.1" + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs3@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" - integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" - core-js-compat "^3.21.0" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" - integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" - -babel-plugin-replace-ts-export-assignment@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-replace-ts-export-assignment/-/babel-plugin-replace-ts-export-assignment-0.0.2.tgz#927a30ba303fcf271108980a8d4f80a693e1d53f" - integrity sha512-BiTEG2Ro+O1spuheL5nB289y37FFmz0ISE6GjpNCG2JuA/WNcuEHSYw01+vN8quGf208sID3FnZFDwVyqX18YQ== + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1286,80 +1231,70 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== dependencies: - fill-range "^7.0.1" + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" -browserslist@^4.20.2, browserslist@^4.20.4: - version "4.21.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.0.tgz#7ab19572361a140ecd1e023e2c1ed95edda0cefe" - integrity sha512-UQxE0DIhRB5z/zDz9iA03BOfxaN2+GQdBYH/2WrSIWEUrnpzTPJbhqt+umq6r3acaPRTW1FNTkrcp0PXgtFkvA== +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: - caniuse-lite "^1.0.30001358" - electron-to-chromium "^1.4.164" - node-releases "^2.0.5" - update-browserslist-db "^1.0.0" + node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -caching-transform@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" - integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== - dependencies: - hasha "^5.0.0" - make-dir "^3.0.0" - package-hash "^4.0.0" - write-file-atomic "^3.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001358: - version "1.0.30001358" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001358.tgz#473d35dabf5e448b463095cab7924e96ccfb8c00" - integrity sha512-hvp8PSRymk85R20bsDra7ZTCpSVGN/PAz9pSAjPSjKC+rNmnUk5vCRgJwiTT/O4feQ/yu/drvZYpKxxhbFuChw== - -chai@^4.2.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" - integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - loupe "^2.3.1" - pathval "^1.1.1" - type-detect "^4.0.5" - -chalk@^2.0.0: +caniuse-lite@^1.0.30001565: + version "1.0.30001570" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" + integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== + +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1368,7 +1303,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1376,54 +1311,56 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -check-error@^1.0.2: +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== - -chokidar@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - -chokidar@^3.4.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +cjs-module-lexer@^1.2.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-table3@^0.6.3, cli-table3@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" cliui@^7.0.2: version "7.0.4" @@ -1434,25 +1371,24 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" -codecov@^3.8.0: - version "3.8.3" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.3.tgz#9c3e364b8a700c597346ae98418d09880a3fdbe7" - integrity sha512-Y8Hw+V3HgR7V71xWH2vQ9lyS358CbGCldWlJFR0JirqoGtOoas3R3/OclRTvgUYFK29mmJICDPauVKmpqbwhOA== - dependencies: - argv "0.0.2" - ignore-walk "3.0.4" - js-yaml "3.14.1" - teeny-request "7.1.1" - urlgrey "1.0.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^1.9.0: version "1.9.3" @@ -1478,266 +1414,352 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.6, combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concurrently@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.5.1.tgz#4518c67f7ac680cf5c34d5adf399a2a2047edc8c" - integrity sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag== - dependencies: - chalk "^4.1.0" - date-fns "^2.16.1" - lodash "^4.17.21" - rxjs "^6.6.3" - spawn-command "^0.0.2-1" - supports-color "^8.1.0" - tree-kill "^1.2.2" - yargs "^16.2.0" - -convert-source-map@^1.1.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -core-js-compat@^3.21.0, core-js-compat@^3.22.1: - version "3.23.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.2.tgz#5cbf8a9c8812d665392845b85ae91b5bcc7b615c" - integrity sha512-lrgZvxFwbQp9v7E8mX0rJ+JX7Bvh4eGULZXA1IAyjlsnWvCdw6TF8Tg6xtaSUSJMrSrMaLdpmk+V54LM1dvfOA== - dependencies: - browserslist "^4.20.4" - semver "7.0.0" - -core-js@^3.22.1: - version "3.23.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.2.tgz#e07a60ca8b14dd129cabdc3d2551baf5a01c76f0" - integrity sha512-ELJOWxNrJfOH/WK4VJ3Qd+fOqZuOuDNDJz0xG6Bt4mGg2eO/UT9CljCrbqDGovjLKUrGajEEBcoTOc0w+yBYeQ== - -cross-spawn@^7.0.0, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" -date-fns@^2.16.1: - version "2.28.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" - integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== - -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@^4.3.4, debug@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== -default-require-extensions@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" - integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== - dependencies: - strip-bom "^4.0.0" +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -diff@^4.0.2: +diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -electron-to-chromium@^1.4.164: - version "1.4.167" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.167.tgz#72424aebc85df12c5331d37b1bcfd1ae01322c55" - integrity sha512-lPHuHXBwpkr4RcfaZBKm6TKOWG/1N9mVggUpP4fY3l1JIUU2x4fkM8928smYdZ5lF+6KCTAxo1aK9JmqT+X71Q== +electron-to-chromium@^1.4.601: + version "1.4.614" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0" + integrity sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" - integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.1" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== -es6-error@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-prettier@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz#99b55d7dd70047886b2222fdd853665f180b36af" + integrity sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.7" + +eslint-plugin-unused-imports@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" + integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.20.1: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -fast-url-parser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - punycode "^1.3.2" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: - to-regex-range "^5.0.1" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" -find-cache-dir@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-sha256@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6" + integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" + reusify "^1.0.4" -find-cache-dir@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" + bser "2.1.1" -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" + flat-cache "^4.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: - locate-path "^3.0.0" + to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" @@ -1747,139 +1769,82 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== - -foreground-child@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" - integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^3.0.2" - -form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + locate-path "^6.0.0" + path-exists "^4.0.0" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fromentries@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" - integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + flatted "^3.2.9" + keyv "^4.5.4" -fs-readdir-recursive@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" - integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.1, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -functions-have-names@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" + is-glob "^4.0.3" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1891,30 +1856,36 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -graceful-fs@^4.1.15: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - -hamming-distance@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hamming-distance/-/hamming-distance-1.0.0.tgz#39bfa46c61f39e87421e4035a1be4f725dd7b931" - integrity sha512-hYz2IIKtyuZGfOqCs7skNiFEATf+v9IUNSOaQSr6Ll4JOxxWhOvXvc3mIdCW82Z3xW+zUoto7N/ssD4bDxAWoA== +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-flag@^3.0.0: version "3.0.0" @@ -1926,80 +1897,62 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hasha@^5.0.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" - integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== dependencies: - is-stream "^2.0.0" - type-fest "^0.8.0" + function-bind "^1.1.2" -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-proxy-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" + safer-buffer ">= 2.1.2 < 3.0.0" -https-proxy-agent@^5.0.0: +ignore-walk@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== dependencies: - agent-base "6" - debug "4" + minimatch "^5.0.1" -ignore-walk@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" - integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: - minimatch "^3.0.4" + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" imurmurhash@^0.1.4: version "0.1.4" @@ -2019,219 +1972,94 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has-tostringtag "^1.0.0" + hasown "^2.0.0" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.1, is-glob@~4.0.1: +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== -istanbul-lib-hook@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" - integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== - dependencies: - append-transform "^2.0.0" - -istanbul-lib-instrument@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: - "@babel/core" "^7.7.5" + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" + istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -istanbul-lib-processinfo@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" - integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== +istanbul-lib-instrument@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" + integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== dependencies: - archy "^1.0.0" - cross-spawn "^7.0.3" + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" istanbul-lib-coverage "^3.2.0" - p-map "^3.0.0" - rimraf "^3.0.0" - uuid "^8.3.2" + semver "^7.5.4" istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" + make-dir "^4.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: @@ -2243,20 +2071,378 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: - version "3.1.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" - integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== +istanbul-reports@^3.1.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.4.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.14.1, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -2264,10 +2450,10 @@ js-yaml@3.14.1, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -2276,38 +2462,65 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -just-extend@^4.0.2: - version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" - integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^5.0.0: version "5.0.0" @@ -2321,244 +2534,210 @@ locate-path@^6.0.0: resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: - p-locate "^5.0.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + p-locate "^5.0.0" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== -lodash@^4.17.15, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -log-symbols@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" +lru-cache@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -loupe@^2.3.1: - version "2.3.4" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" - integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: - get-func-name "^2.0.0" + yallist "^3.0.2" -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pify "^4.0.1" - semver "^5.6.0" + yallist "^4.0.0" -make-dir@^3.0.0, make-dir@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +marked-terminal@^7.1.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" + integrity sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ== + dependencies: + ansi-escapes "^7.0.0" + ansi-regex "^6.1.0" + chalk "^5.3.0" + cli-highlight "^2.1.11" + cli-table3 "^0.6.5" + node-emoji "^2.1.3" + supports-hyperlinks "^3.1.0" + +marked@^9.1.2: + version "9.1.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" + integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - mime-db "1.52.0" + braces "^3.0.3" + picomatch "^2.3.1" -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" -mocha@^8.1.1: - version "8.4.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" - integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.1" - debug "4.3.1" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.1.6" - growl "1.10.5" - he "1.2.0" - js-yaml "4.0.0" - log-symbols "4.0.0" - minimatch "3.0.4" - ms "2.1.3" - nanoid "3.1.20" - serialize-javascript "5.0.1" - strip-json-comments "3.1.1" - supports-color "8.1.1" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.1.0" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - -nise@^4.0.4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" - integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@sinonjs/fake-timers" "^6.0.0" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - -nock@^13.2.7: - version "13.2.7" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.7.tgz#c93933b61df42f4f4b3a07fde946a4e209c0c168" - integrity sha512-R6NUw7RIPtKwgK7jskuKoEi4VFMqIHtV2Uu9K/Uegc4TA5cqe+oNMYslZcUmnVNQCTG6wcSqUBaGTDd7sq5srg== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.21" - propagate "^2.0.0" + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" -node-environment-flags@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-fetch@^2.6.1: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== +node-emoji@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" + integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== dependencies: - whatwg-url "^5.0.0" + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" -node-preload@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" - integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== - dependencies: - process-on-spawn "^1.0.0" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" - integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -normalize-path@^3.0.0, normalize-path@~3.0.0: +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nyc@^15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" - integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== dependencies: - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - caching-transform "^4.0.0" - convert-source-map "^1.7.0" - decamelize "^1.2.0" - find-cache-dir "^3.2.0" - find-up "^4.1.0" - foreground-child "^2.0.0" - get-package-type "^0.1.0" - glob "^7.1.6" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-hook "^3.0.0" - istanbul-lib-instrument "^4.0.0" - istanbul-lib-processinfo "^2.0.2" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - make-dir "^3.0.0" - node-preload "^0.2.1" - p-map "^3.0.0" - process-on-spawn "^1.0.0" - resolve-from "^5.0.0" - rimraf "^3.0.0" - signal-exit "^3.0.2" - spawn-wrap "^2.0.0" - test-exclude "^6.0.0" - yargs "^15.0.2" + npm-normalize-package-bin "^2.0.0" -object-inspect@^1.12.0, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== -object.assign@^4.1.0, object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== +npm-packlist@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" -object.getownpropertydescriptors@^2.0.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" - integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: - array.prototype.reduce "^1.0.4" - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.1" + path-key "^3.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== once@^1.3.0: version "1.4.0" @@ -2567,27 +2746,46 @@ once@^1.3.0: dependencies: wrappy "1" -p-limit@^2.0.0, p-limit@^2.2.0: +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-all@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" + integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== + dependencies: + p-map "^4.0.0" + +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -2602,10 +2800,10 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" @@ -2614,25 +2812,39 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-hash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" - integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: - graceful-fs "^4.1.15" - hasha "^5.0.0" - lodash.flattendeep "^4.4.0" - release-zalgo "^1.0.0" + callsites "^3.0.0" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== path-exists@^4.0.0: version "4.0.0" @@ -2644,7 +2856,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -2654,240 +2866,191 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -pathval@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" - integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1: +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pirates@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0: +pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" -process-on-spawn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" - integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== - dependencies: - fromentries "^1.2.0" - -propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== dependencies: - safe-buffer "^5.1.0" + fast-diff "^1.1.2" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== - dependencies: - picomatch "^2.2.1" +prettier@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" + integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - picomatch "^2.2.1" + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + kleur "^3.0.3" + sisteransi "^1.0.5" -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== +publint@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" + integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== dependencies: - "@babel/runtime" "^7.8.4" + npm-packlist "^5.1.3" + picocolors "^1.1.1" + sade "^1.8.1" -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== -regexpu-core@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" - integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" +pure-rand@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== - dependencies: - jsesc "~0.5.0" +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -release-zalgo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" - integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: - es6-error "^4.0.1" + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.14.2: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: - glob "^7.1.3" + queue-microtask "^1.2.2" -rxjs@^6.6.3: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== dependencies: - tslib "^1.9.0" + mri "^1.1.0" -safe-buffer@^5.1.0: +safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@^5.6.0, semver@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -serialize-javascript@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== +semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: - randombytes "^2.1.0" + lru-cache "^6.0.0" -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +semver@^7.5.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" +semver@^7.6.0: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== shebang-command@^2.0.0: version "2.0.0" @@ -2901,41 +3064,32 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.2: +signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -sinon@^9.2.0: - version "9.2.4" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" - integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== - dependencies: - "@sinonjs/commons" "^1.8.1" - "@sinonjs/fake-timers" "^6.0.1" - "@sinonjs/samsam" "^5.3.1" - diff "^4.0.2" - nise "^4.0.4" - supports-color "^7.1.0" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -slash@^2.0.0: +skin-tone@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" -source-map-support@^0.5.16: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2945,44 +3099,42 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -spawn-command@^0.0.2-1: - version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" - integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg== - -spawn-wrap@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" - integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== - dependencies: - foreground-child "^2.0.0" - is-windows "^1.0.2" - make-dir "^3.0.0" - rimraf "^3.0.0" - signal-exit "^3.0.2" - which "^2.0.1" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: - stubs "^3.0.0" + escape-string-regexp "^2.0.0" -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +standardwebhooks@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/standardwebhooks/-/standardwebhooks-1.0.0.tgz#5faa23ceacbf9accd344361101d9e3033b64324f" + integrity sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg== + dependencies: + "@stablelib/base64" "^1.0.0" + fast-sha256 "^1.3.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2991,30 +3143,12 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - ansi-regex "^3.0.0" + safe-buffer "~5.2.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" @@ -3023,27 +3157,30 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-json-comments@3.1.1: +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== - -supports-color@8.1.1, supports-color@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== supports-color@^5.3.0: version "5.5.0" @@ -3052,28 +3189,39 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -teeny-request@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.1.1.tgz#2b0d156f4a8ad81de44303302ba8d7f1f05e20e6" - integrity sha512-iwY6rkW5DDGq8hE2YgNQlKbptYpY5Nn2xecjQiNjOXWbKzPGUfmeUBCSQbbr306d7Z7U2N0TPl+/SwYRfua1Dg== +synckit@^0.11.7: + version "0.11.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - stream-events "^1.0.5" - uuid "^8.0.0" + "@pkgr/core" "^0.2.4" test-exclude@^6.0.0: version "6.0.0" @@ -3084,6 +3232,25 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -3096,165 +3263,177 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + +ts-jest@^29.1.0: + version "29.1.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +ts-node@^10.5.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" + integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.0" + yn "3.1.1" + +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" + dependencies: + debug "^4.3.7" + fast-glob "^3.3.2" + get-stdin "^8.0.0" + p-all "^3.0.0" + picocolors "^1.1.1" + signal-exit "^3.0.7" + string-to-stream "^3.0.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" + +tsconfig-paths@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" -type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: +type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.8.0: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@^4.3.2: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript-eslint@8.31.1: + version "8.31.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b" + integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.31.1" + "@typescript-eslint/parser" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + +typescript@5.6.1-rc: + version "5.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" + integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== + +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== -update-browserslist-db@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" - integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" -urlgrey@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-1.0.0.tgz#72d2f904482d0b602e3c7fa599343d699bbe1017" - integrity sha512-hJfIzMPJmI9IlLkby8QrsCykQ+SXDeO2W5Q9QTW3QpqZVTx4a/K7p8/5q+/isD8vsbVaFgql/gvAoQCRQ2Cb5w== +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: - fast-url-parser "^1.1.3" - -uuid@^8.0.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + punycode "^2.1.0" -v8flags@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" - integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== - dependencies: - homedir-polyfill "^1.0.1" +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +v8-compile-cache-lib@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" + integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" +validate-npm-package-name@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" + integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" -which@2.0.2, which@^2.0.1: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -workerpool@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" - integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3269,55 +3448,40 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + signal-exit "^3.0.7" y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@16.2.0, yargs@^16.2.0: +yargs@^16.0.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -3330,22 +3494,23 @@ yargs@16.2.0, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^15.0.2: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" From 9ea85e33b6484aa6a62c178c51d9522756750297 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:53:09 +0000 Subject: [PATCH 02/73] chore: update SDK settings --- .github/workflows/release-doctor.yml | 20 ++++++++ .release-please-manifest.json | 3 ++ .stats.yml | 2 +- CONTRIBUTING.md | 6 +-- README.md | 4 +- bin/check-release-environment | 18 +++++++ package.json | 2 +- packages/mcp-server/README.md | 10 ++-- packages/mcp-server/package.json | 4 +- release-please-config.json | 73 ++++++++++++++++++++++++++++ src/version.ts | 2 +- 11 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/release-doctor.yml create mode 100644 .release-please-manifest.json create mode 100644 bin/check-release-environment create mode 100644 release-please-config.json diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 00000000..a2da4ddd --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,20 @@ +name: Release Doctor +on: + pull_request: + branches: + - stainless + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'imagekit-developer/imagekit-nodejs' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..67dcd73f --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.1-alpha.0" +} diff --git a/.stats.yml b/.stats.yml index 67507d30..c328b0a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 -config_hash: 1dd1a96eff228aa2567b9973c36f5593 +config_hash: 93458331374b86a886e325d435070b07 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ceb97606..b7920529 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,15 +42,15 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ npm install git+ssh://git@github.com:stainless-sdks/imagekit-typescript.git +$ npm install git+ssh://git@github.com:imagekit-developer/imagekit-nodejs.git ``` Alternatively, to link a local copy of the repo: ```sh # Clone -$ git clone https://www.github.com/stainless-sdks/imagekit-typescript -$ cd imagekit-typescript +$ git clone https://www.github.com/imagekit-developer/imagekit-nodejs +$ cd imagekit-nodejs # With yarn $ yarn link diff --git a/README.md b/README.md index 3282e615..cbefce31 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It is generated with [Stainless](https://www.stainless.com/). ## Installation ```sh -npm install git+ssh://git@github.com:stainless-sdks/imagekit-typescript.git +npm install git+ssh://git@github.com:imagekit-developer/imagekit-nodejs.git ``` > [!NOTE] @@ -379,7 +379,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/imagekit-typescript/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/imagekit-developer/imagekit-nodejs/issues) with questions, bugs, or suggestions. ## Requirements diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 00000000..6b43775a --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +errors=() + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" + diff --git a/package.json b/package.json index 7eaf6316..ddb4b7de 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "types": "dist/index.d.ts", "main": "dist/index.js", "type": "commonjs", - "repository": "github:stainless-sdks/imagekit-typescript", + "repository": "github:imagekit-developer/imagekit-nodejs", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "files": [ diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index a6726151..d1fba8cc 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -9,8 +9,8 @@ It is generated with [Stainless](https://www.stainless.com/). Because it's not published yet, clone the repo and build it: ```sh -git clone git@github.com:stainless-sdks/imagekit-typescript.git -cd imagekit-typescript +git clone git@github.com:imagekit-developer/imagekit-nodejs.git +cd imagekit-nodejs ./scripts/bootstrap ./scripts/build ``` @@ -41,11 +41,7 @@ For clients with a configuration JSON, it might look something like this: "mcpServers": { "imagekit_nodejs_api": { "command": "node", - "args": [ - "/path/to/local/imagekit-typescript/packages/mcp-server", - "--client=claude", - "--tools=dynamic" - ], + "args": ["/path/to/local/imagekit-nodejs/packages/mcp-server", "--client=claude", "--tools=dynamic"], "env": { "IMAGEKIT_PRIVATE_API_KEY": "My Private API Key", "ORG_MY_PASSWORD_TOKEN": "My Password" diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 09d365b5..f7b04a2e 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -8,10 +8,10 @@ "type": "commonjs", "repository": { "type": "git", - "url": "git+https://github.com/stainless-sdks/imagekit-typescript.git", + "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git", "directory": "packages/mcp-server" }, - "homepage": "https://github.com/stainless-sdks/imagekit-typescript/tree/main/packages/mcp-server#readme", + "homepage": "https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server#readme", "license": "Apache-2.0", "packageManager": "yarn@1.22.22", "private": false, diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..b1909804 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,73 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "node", + "extra-files": [ + "src/version.ts", + "README.md", + "packages/mcp-server/yarn.lock", + { + "type": "json", + "path": "packages/mcp-server/package.json", + "jsonpath": "$.version" + } + ] +} diff --git a/src/version.ts b/src/version.ts index 55a1a527..db692bc9 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.0.1-alpha.0'; +export const VERSION = '0.0.1-alpha.0'; // x-release-please-version From ace190977c46f6702597fb4d6ea54133346724a2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:59:38 +0000 Subject: [PATCH 03/73] feat(api): manual updates --- .github/workflows/release-doctor.yml | 2 +- .stats.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index a2da4ddd..678e0297 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -2,7 +2,7 @@ name: Release Doctor on: pull_request: branches: - - stainless + - master workflow_dispatch: jobs: diff --git a/.stats.yml b/.stats.yml index c328b0a5..57157abd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 -config_hash: 93458331374b86a886e325d435070b07 +config_hash: 0388cd86c33cbbc99abfb19c4abb324f From 693e3cf68ccd5a8de740ed35b9d0cc2660e88521 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:17:17 +0000 Subject: [PATCH 04/73] feat(api): manual updates --- .github/workflows/ci.yml | 18 +- .prettierignore | 2 +- .stats.yml | 2 +- eslint.config.mjs | 2 +- packages/mcp-server/README.md | 363 -- packages/mcp-server/build | 32 - packages/mcp-server/jest.config.ts | 17 - packages/mcp-server/package.json | 85 - .../scripts/postprocess-dist-package-json.cjs | 12 - packages/mcp-server/src/code-tool-paths.cts | 3 - packages/mcp-server/src/code-tool-types.ts | 14 - packages/mcp-server/src/code-tool-worker.ts | 46 - packages/mcp-server/src/code-tool.ts | 145 - packages/mcp-server/src/compat.ts | 483 --- packages/mcp-server/src/dynamic-tools.ts | 153 - packages/mcp-server/src/filtering.ts | 14 - packages/mcp-server/src/headers.ts | 31 - packages/mcp-server/src/http.ts | 115 - packages/mcp-server/src/index.ts | 108 - packages/mcp-server/src/options.ts | 456 --- packages/mcp-server/src/server.ts | 180 - packages/mcp-server/src/stdio.ts | 13 - packages/mcp-server/src/tools.ts | 1 - .../origins/create-accounts-origins.ts | 338 -- .../origins/delete-accounts-origins.ts | 43 - .../accounts/origins/get-accounts-origins.ts | 41 - .../accounts/origins/list-accounts-origins.ts | 35 - .../origins/update-accounts-origins.ts | 345 -- .../create-accounts-url-endpoints.ts | 103 - .../delete-accounts-url-endpoints.ts | 43 - .../get-accounts-url-endpoints.ts | 49 - .../list-accounts-url-endpoints.ts | 44 - .../update-accounts-url-endpoints.ts | 112 - .../accounts/usage/get-accounts-usage.ts | 56 - .../src/tools/assets/list-assets.ts | 94 - .../beta/v2/files/upload-v2-beta-files.ts | 309 -- .../invalidation/create-cache-invalidation.ts | 46 - .../invalidation/get-cache-invalidation.ts | 47 - .../create-custom-metadata-fields.ts | 154 - .../delete-custom-metadata-fields.ts | 47 - .../list-custom-metadata-fields.ts | 48 - .../update-custom-metadata-fields.ts | 150 - .../tools/files/bulk/add-tags-files-bulk.ts | 56 - .../src/tools/files/bulk/delete-files-bulk.ts | 49 - .../files/bulk/remove-ai-tags-files-bulk.ts | 56 - .../files/bulk/remove-tags-files-bulk.ts | 56 - .../mcp-server/src/tools/files/copy-files.ts | 55 - .../src/tools/files/delete-files.ts | 41 - .../mcp-server/src/tools/files/get-files.ts | 47 - .../files/metadata/get-files-metadata.ts | 47 - .../metadata/get-from-url-files-metadata.ts | 48 - .../mcp-server/src/tools/files/move-files.ts | 50 - .../src/tools/files/rename-files.ts | 58 - .../src/tools/files/update-files.ts | 192 - .../src/tools/files/upload-files.ts | 325 -- .../files/versions/delete-files-versions.ts | 52 - .../files/versions/get-files-versions.ts | 50 - .../files/versions/list-files-versions.ts | 47 - .../files/versions/restore-files-versions.ts | 52 - .../src/tools/folders/copy-folders.ts | 55 - .../src/tools/folders/create-folders.ts | 52 - .../src/tools/folders/delete-folders.ts | 48 - .../src/tools/folders/job/get-folders-job.ts | 47 - .../src/tools/folders/move-folders.ts | 50 - .../src/tools/folders/rename-folders.ts | 56 - packages/mcp-server/src/tools/index.ts | 153 - packages/mcp-server/src/tools/types.ts | 103 - packages/mcp-server/tests/compat.test.ts | 1166 ------ .../mcp-server/tests/dynamic-tools.test.ts | 185 - packages/mcp-server/tests/options.test.ts | 518 --- packages/mcp-server/tests/tools.test.ts | 225 - packages/mcp-server/tsc-multi.json | 7 - packages/mcp-server/tsconfig.build.json | 18 - packages/mcp-server/tsconfig.dist-src.json | 11 - packages/mcp-server/tsconfig.json | 37 - packages/mcp-server/yarn.lock | 3606 ----------------- release-please-config.json | 11 +- scripts/build | 6 - scripts/build-all | 5 - scripts/publish-packages.ts | 102 - scripts/utils/make-dist-package-json.cjs | 8 - 81 files changed, 7 insertions(+), 12142 deletions(-) delete mode 100644 packages/mcp-server/README.md delete mode 100644 packages/mcp-server/build delete mode 100644 packages/mcp-server/jest.config.ts delete mode 100644 packages/mcp-server/package.json delete mode 100644 packages/mcp-server/scripts/postprocess-dist-package-json.cjs delete mode 100644 packages/mcp-server/src/code-tool-paths.cts delete mode 100644 packages/mcp-server/src/code-tool-types.ts delete mode 100644 packages/mcp-server/src/code-tool-worker.ts delete mode 100644 packages/mcp-server/src/code-tool.ts delete mode 100644 packages/mcp-server/src/compat.ts delete mode 100644 packages/mcp-server/src/dynamic-tools.ts delete mode 100644 packages/mcp-server/src/filtering.ts delete mode 100644 packages/mcp-server/src/headers.ts delete mode 100644 packages/mcp-server/src/http.ts delete mode 100644 packages/mcp-server/src/index.ts delete mode 100644 packages/mcp-server/src/options.ts delete mode 100644 packages/mcp-server/src/server.ts delete mode 100644 packages/mcp-server/src/stdio.ts delete mode 100644 packages/mcp-server/src/tools.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts delete mode 100644 packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts delete mode 100644 packages/mcp-server/src/tools/assets/list-assets.ts delete mode 100644 packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts delete mode 100644 packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts delete mode 100644 packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts delete mode 100644 packages/mcp-server/src/tools/files/copy-files.ts delete mode 100644 packages/mcp-server/src/tools/files/delete-files.ts delete mode 100644 packages/mcp-server/src/tools/files/get-files.ts delete mode 100644 packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts delete mode 100644 packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts delete mode 100644 packages/mcp-server/src/tools/files/move-files.ts delete mode 100644 packages/mcp-server/src/tools/files/rename-files.ts delete mode 100644 packages/mcp-server/src/tools/files/update-files.ts delete mode 100644 packages/mcp-server/src/tools/files/upload-files.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/delete-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/get-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/list-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/files/versions/restore-files-versions.ts delete mode 100644 packages/mcp-server/src/tools/folders/copy-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/create-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/delete-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/job/get-folders-job.ts delete mode 100644 packages/mcp-server/src/tools/folders/move-folders.ts delete mode 100644 packages/mcp-server/src/tools/folders/rename-folders.ts delete mode 100644 packages/mcp-server/src/tools/index.ts delete mode 100644 packages/mcp-server/src/tools/types.ts delete mode 100644 packages/mcp-server/tests/compat.test.ts delete mode 100644 packages/mcp-server/tests/dynamic-tools.test.ts delete mode 100644 packages/mcp-server/tests/options.test.ts delete mode 100644 packages/mcp-server/tests/tools.test.ts delete mode 100644 packages/mcp-server/tsc-multi.json delete mode 100644 packages/mcp-server/tsconfig.build.json delete mode 100644 packages/mcp-server/tsconfig.dist-src.json delete mode 100644 packages/mcp-server/tsconfig.json delete mode 100644 packages/mcp-server/yarn.lock delete mode 100755 scripts/build-all delete mode 100644 scripts/publish-packages.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 870fe3ab..3f5e57c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '20' - name: Bootstrap run: ./scripts/bootstrap @@ -46,7 +46,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '20' - name: Bootstrap run: ./scripts/bootstrap @@ -68,15 +68,6 @@ jobs: AUTH: ${{ steps.github-oidc.outputs.github_token }} SHA: ${{ github.sha }} run: ./scripts/utils/upload-artifact.sh - - - name: Upload MCP Server tarball - if: github.repository == 'stainless-sdks/imagekit-typescript' - env: - URL: https://pkg.stainless.com/s?subpackage=mcp-server - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - BUILD_PATH: packages/mcp-server/dist - run: ./scripts/utils/upload-artifact.sh test: timeout-minutes: 10 name: test @@ -88,13 +79,10 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '20' - name: Bootstrap run: ./scripts/bootstrap - - name: Build - run: ./scripts/build - - name: Run tests run: ./scripts/test diff --git a/.prettierignore b/.prettierignore index 7cc13dd1..3548c5af 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,4 @@ CHANGELOG.md /deno # don't format tsc output, will break source maps -dist +/dist diff --git a/.stats.yml b/.stats.yml index 57157abd..9af54678 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 -config_hash: 0388cd86c33cbbc99abfb19c4abb324f +config_hash: 1335a5f946838eb26cf469ddf59cd223 diff --git a/eslint.config.mjs b/eslint.config.mjs index c1a01a62..68d1b0b9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -34,7 +34,7 @@ export default tseslint.config( }, }, { - files: ['tests/**', 'examples/**', 'packages/**'], + files: ['tests/**', 'examples/**'], rules: { 'no-restricted-imports': 'off', }, diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md deleted file mode 100644 index d1fba8cc..00000000 --- a/packages/mcp-server/README.md +++ /dev/null @@ -1,363 +0,0 @@ -# Image Kit TypeScript MCP Server - -It is generated with [Stainless](https://www.stainless.com/). - -## Installation - -### Building - -Because it's not published yet, clone the repo and build it: - -```sh -git clone git@github.com:imagekit-developer/imagekit-nodejs.git -cd imagekit-nodejs -./scripts/bootstrap -./scripts/build -``` - -### Running - -```sh -# set env vars as needed -export IMAGEKIT_PRIVATE_API_KEY="My Private API Key" -export ORG_MY_PASSWORD_TOKEN="My Password" -node ./packages/mcp-server/dist/index.js -``` - -> [!NOTE] -> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y @imagekit/nodejs-mcp` - -### Via MCP Client - -[Build the project](#building) as mentioned above. - -There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already -have a client, consult their documentation to install the MCP server. - -For clients with a configuration JSON, it might look something like this: - -```json -{ - "mcpServers": { - "imagekit_nodejs_api": { - "command": "node", - "args": ["/path/to/local/imagekit-nodejs/packages/mcp-server", "--client=claude", "--tools=dynamic"], - "env": { - "IMAGEKIT_PRIVATE_API_KEY": "My Private API Key", - "ORG_MY_PASSWORD_TOKEN": "My Password" - } - } - } -} -``` - -## Exposing endpoints to your MCP Client - -There are two ways to expose endpoints as tools in the MCP server: - -1. Exposing one tool per endpoint, and filtering as necessary -2. Exposing a set of tools to dynamically discover and invoke endpoints from the API - -### Filtering endpoints and tools - -You can run the package on the command line to discover and filter the set of tools that are exposed by the -MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's -context window. - -You can filter by multiple aspects: - -- `--tool` includes a specific tool by name -- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` -- `--operation` includes just read (get/list) or just write operations - -### Dynamic tools - -If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will -expose the following tools: - -1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query -2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint -3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters - -This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all -of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to -search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it -can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, -you can opt-in to explicit tools, the dynamic tools, or both. - -See more information with `--help`. - -All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). - -Use `--list` to see the list of available tools, or see below. - -### Specifying the MCP Client - -Different clients have varying abilities to handle arbitrary tools and schemas. - -You can specify the client you are using with the `--client` argument, and the MCP server will automatically -serve tools and schemas that are more compatible with that client. - -- `--client=`: Set all capabilities based on a known MCP client - - - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` - - Example: `--client=cursor` - -Additionally, if you have a client not on the above list, or the client has gotten better -over time, you can manually enable or disable certain capabilities: - -- `--capability=`: Specify individual client capabilities - - Available capabilities: - - `top-level-unions`: Enable support for top-level unions in tool schemas - - `valid-json`: Enable JSON string parsing for arguments - - `refs`: Enable support for $ref pointers in schemas - - `unions`: Enable support for union types (anyOf) in schemas - - `formats`: Enable support for format validations in schemas (e.g. date-time, email) - - `tool-name-length=N`: Set maximum tool name length to N characters - - Example: `--capability=top-level-unions --capability=tool-name-length=40` - - Example: `--capability=top-level-unions,tool-name-length=40` - -### Examples - -1. Filter for read operations on cards: - -```bash ---resource=cards --operation=read -``` - -2. Exclude specific tools while including others: - -```bash ---resource=cards --no-tool=create_cards -``` - -3. Configure for Cursor client with custom max tool name length: - -```bash ---client=cursor --capability=tool-name-length=40 -``` - -4. Complex filtering with multiple criteria: - -```bash ---resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards -``` - -## Running remotely - -Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket. - -Authorization can be provided via the `Authorization` header using the Basic scheme. - -Additionally, authorization can be provided via the following headers: -| Header | Equivalent client option | Security scheme | -| ---------------------------- | ------------------------ | --------------- | -| `x-imagekit-private-api-key` | `privateAPIKey` | basicAuth | -| `x-org-my-password-token` | `password` | basicAuth | - -A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: - -```json -{ - "mcpServers": { - "imagekit_nodejs_api": { - "url": "http://localhost:3000", - "headers": { - "Authorization": "Basic " - } - } - } -} -``` - -The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. -For example, to exclude specific tools while including others, use the URL: - -``` -http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards -``` - -Or, to configure for the Cursor client, with a custom max tool name length, use the URL: - -``` -http://localhost:3000?client=cursor&capability=tool-name-length%3D40 -``` - -## Importing the tools and server individually - -```js -// Import the server, generated endpoints, or the init function -import { server, endpoints, init } from "@imagekit/nodejs-mcp/server"; - -// import a specific tool -import createCustomMetadataFields from "@imagekit/nodejs-mcp/tools/custom-metadata-fields/create-custom-metadata-fields"; - -// initialize the server and all endpoints -init({ server, endpoints }); - -// manually start server -const transport = new StdioServerTransport(); -await server.connect(transport); - -// or initialize your own server with specific tools -const myServer = new McpServer(...); - -// define your own endpoint -const myCustomEndpoint = { - tool: { - name: 'my_custom_tool', - description: 'My custom tool', - inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), - }, - handler: async (client: client, args: any) => { - return { myResponse: 'Hello world!' }; - }) -}; - -// initialize the server with your custom endpoints -init({ server: myServer, endpoints: [createCustomMetadataFields, myCustomEndpoint] }); -``` - -## Available Tools - -The following tools are available in this MCP server. - -### Resource `customMetadataFields`: - -- `create_custom_metadata_fields` (`write`): This API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API. -- `update_custom_metadata_fields` (`write`): This API updates the label or schema of an existing custom metadata field. -- `list_custom_metadata_fields` (`read`): This API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response. -- `delete_custom_metadata_fields` (`write`): This API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name. - -### Resource `files`: - -- `update_files` (`write`): This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API. -- `delete_files` (`write`): This API deletes the file and all its file versions permanently. - - Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. - -- `copy_files` (`write`): This will copy a file from one folder to another. - - Note: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history. - -- `get_files` (`read`): This API returns an object with details or attributes about the current version of the file. -- `move_files` (`write`): This will move a file and all its versions from one folder to another. - - Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file. - -- `rename_files` (`write`): You can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. - - Note: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested. - -- `upload_files` (`write`): ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload. - - The [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT. - - **File size limit** \ - On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans. - - **Version limit** \ - A file can have a maximum of 100 versions. - - **Demo applications** - - - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. - - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. - -### Resource `files.bulk`: - -- `delete_files_bulk` (`write`): This API deletes multiple files and all their file versions permanently. - - Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. - - A maximum of 100 files can be deleted at a time. - -- `add_tags_files_bulk` (`write`): This API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time. -- `remove_ai_tags_files_bulk` (`write`): This API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time. -- `remove_tags_files_bulk` (`write`): This API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time. - -### Resource `files.versions`: - -- `list_files_versions` (`read`): This API returns details of all versions of a file. -- `delete_files_versions` (`write`): This API deletes a non-current file version permanently. The API returns an empty response. - - Note: If you want to delete all versions of a file, use the delete file API. - -- `get_files_versions` (`read`): This API returns an object with details or attributes of a file version. -- `restore_files_versions` (`write`): This API restores a file version as the current file version. - -### Resource `files.metadata`: - -- `get_files_metadata` (`read`): You can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API. - - You can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter. - -- `get_from_url_files_metadata` (`read`): Get image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API. - -### Resource `assets`: - -- `list_assets` (`read`): This API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`. - -### Resource `cache.invalidation`: - -- `create_cache_invalidation` (`write`): This API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes. -- `get_cache_invalidation` (`read`): This API returns the status of a purge cache request. - -### Resource `folders`: - -- `create_folders` (`write`): This will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created. -- `delete_folders` (`write`): This will delete a folder and all its contents permanently. The API returns an empty response. -- `copy_folders` (`write`): This will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. -- `move_folders` (`write`): This will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. -- `rename_folders` (`write`): This API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name. - -### Resource `folders.job`: - -- `get_folders_job` (`read`): This API returns the status of a bulk job like copy and move folder operations. - -### Resource `accounts.usage`: - -- `get_accounts_usage` (`read`): Get the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date. - -### Resource `accounts.origins`: - -- `create_accounts_origins` (`write`): **Note:** This API is currently in beta. - Creates a new origin and returns the origin object. -- `update_accounts_origins` (`write`): **Note:** This API is currently in beta. - Updates the origin identified by `id` and returns the updated origin object. -- `list_accounts_origins` (`read`): **Note:** This API is currently in beta. - Returns an array of all configured origins for the current account. -- `delete_accounts_origins` (`write`): **Note:** This API is currently in beta. - Permanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error. -- `get_accounts_origins` (`read`): **Note:** This API is currently in beta. - Retrieves the origin identified by `id`. - -### Resource `accounts.urlEndpoints`: - -- `create_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Creates a new URL‑endpoint and returns the resulting object. -- `update_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Updates the URL‑endpoint identified by `id` and returns the updated object. -- `list_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. - Returns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation. -- `delete_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. - Deletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation. -- `get_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. - Retrieves the URL‑endpoint identified by `id`. - -### Resource `beta.v2.files`: - -- `upload_v2_beta_files` (`write`): The V2 API enhances security by verifying the entire payload using JWT. This API is in beta. - - ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload. - - **File size limit** \ - On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans. - - **Version limit** \ - A file can have a maximum of 100 versions. - - **Demo applications** - - - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. - - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. diff --git a/packages/mcp-server/build b/packages/mcp-server/build deleted file mode 100644 index 2eede586..00000000 --- a/packages/mcp-server/build +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -rm -rf dist; mkdir dist - -# Copy src to dist/src and build from dist/src into dist, so that -# the source map for index.js.map will refer to ./src/index.ts etc -cp -rp src README.md dist - -for file in LICENSE; do - if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi -done - -for file in CHANGELOG.md; do - if [ -e "${file}" ]; then cp "${file}" dist; fi -done - -# this converts the export map paths for the dist directory -# and does a few other minor things -PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json - -# updates the `@imagekit/nodejs` dependency to point to NPM -node scripts/postprocess-dist-package-json.cjs - -# build to .js/.mjs/.d.ts files -./node_modules/.bin/tsc-multi - -cp tsconfig.dist-src.json dist/src/tsconfig.json - -chmod +x dist/index.js - -DIST_PATH=./dist PKG_IMPORT_PATH=@imagekit/nodejs-mcp/ node ../../scripts/utils/postprocess-files.cjs diff --git a/packages/mcp-server/jest.config.ts b/packages/mcp-server/jest.config.ts deleted file mode 100644 index 6c2868f4..00000000 --- a/packages/mcp-server/jest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { JestConfigWithTsJest } from 'ts-jest'; - -const config: JestConfigWithTsJest = { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - transform: { - '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], - }, - moduleNameMapper: { - '^@imagekit/nodejs-mcp$': '/src/index.ts', - '^@imagekit/nodejs-mcp/(.*)$': '/src/$1', - }, - modulePathIgnorePatterns: ['/dist/'], - testPathIgnorePatterns: ['scripts'], -}; - -export default config; diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json deleted file mode 100644 index f7b04a2e..00000000 --- a/packages/mcp-server/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@imagekit/nodejs-mcp", - "version": "0.0.1-alpha.0", - "description": "The official MCP Server for the Image Kit API", - "author": "Image Kit ", - "types": "dist/index.d.ts", - "main": "dist/index.js", - "type": "commonjs", - "repository": { - "type": "git", - "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git", - "directory": "packages/mcp-server" - }, - "homepage": "https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server#readme", - "license": "Apache-2.0", - "packageManager": "yarn@1.22.22", - "private": false, - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "jest", - "build": "bash ./build", - "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", - "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", - "format": "prettier --write --cache --cache-strategy metadata . !dist", - "prepare": "npm run build", - "tsn": "ts-node -r tsconfig-paths/register", - "lint": "eslint --ext ts,js .", - "fix": "eslint --fix --ext ts,js ." - }, - "dependencies": { - "@imagekit/nodejs": "file:../../dist/", - "@cloudflare/cabidela": "^0.2.4", - "@modelcontextprotocol/sdk": "^1.11.5", - "@valtown/deno-http-worker": "^0.0.21", - "cors": "^2.8.5", - "express": "^5.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", - "qs": "^6.14.0", - "yargs": "^17.7.2", - "zod": "^3.25.20", - "zod-to-json-schema": "^3.24.5", - "zod-validation-error": "^4.0.1" - }, - "bin": { - "mcp-server": "dist/index.js" - }, - "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.3", - "@types/jest": "^29.4.0", - "@types/qs": "^6.14.0", - "@types/yargs": "^17.0.8", - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "eslint": "^8.49.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-unused-imports": "^3.0.0", - "jest": "^29.4.0", - "prettier": "^3.0.0", - "ts-jest": "^29.1.0", - "ts-morph": "^19.0.0", - "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "typescript": "5.8.3" - }, - "imports": { - "@imagekit/nodejs-mcp": ".", - "@imagekit/nodejs-mcp/*": "./src/*" - }, - "exports": { - ".": { - "require": "./dist/index.js", - "default": "./dist/index.mjs" - }, - "./*.mjs": "./dist/*.mjs", - "./*.js": "./dist/*.js", - "./*": { - "require": "./dist/*.js", - "default": "./dist/*.mjs" - } - } -} diff --git a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs deleted file mode 100644 index 2c75a6cd..00000000 --- a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs +++ /dev/null @@ -1,12 +0,0 @@ -const fs = require('fs'); -const pkgJson = require('../dist/package.json'); -const parentPkgJson = require('../../../package.json'); - -for (const dep in pkgJson.dependencies) { - // ensure we point to NPM instead of a local directory - if (dep === '@imagekit/nodejs') { - pkgJson.dependencies[dep] = '^' + parentPkgJson.version; - } -} - -fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2)); diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts deleted file mode 100644 index 15ce7f55..00000000 --- a/packages/mcp-server/src/code-tool-paths.cts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts deleted file mode 100644 index 02e7e890..00000000 --- a/packages/mcp-server/src/code-tool-types.ts +++ /dev/null @@ -1,14 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { ClientOptions } from '@imagekit/nodejs'; - -export type WorkerInput = { - opts: ClientOptions; - code: string; -}; -export type WorkerSuccess = { - result: unknown | null; - logLines: string[]; - errLines: string[]; -}; -export type WorkerError = { message: string | undefined }; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts deleted file mode 100644 index 865c3928..00000000 --- a/packages/mcp-server/src/code-tool-worker.ts +++ /dev/null @@ -1,46 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import util from 'node:util'; -import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; -import { ImageKit } from '@imagekit/nodejs'; - -const fetch = async (req: Request): Promise => { - const { opts, code } = (await req.json()) as WorkerInput; - const client = new ImageKit({ - ...opts, - }); - - const logLines: string[] = []; - const errLines: string[] = []; - const console = { - log: (...args: unknown[]) => { - logLines.push(util.format(...args)); - }, - error: (...args: unknown[]) => { - errLines.push(util.format(...args)); - }, - }; - try { - let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); - const result = await run_(client); - return Response.json({ - result, - logLines, - errLines, - } satisfies WorkerSuccess); - } catch (e) { - const message = e instanceof Error ? e.message : undefined; - return Response.json( - { - message, - } satisfies WorkerError, - { status: 400, statusText: 'Code execution error' }, - ); - } -}; - -export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts deleted file mode 100644 index e0e2d2e7..00000000 --- a/packages/mcp-server/src/code-tool.ts +++ /dev/null @@ -1,145 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { dirname } from 'node:path'; -import { pathToFileURL } from 'node:url'; -import ImageKit, { ClientOptions } from '@imagekit/nodejs'; -import { Endpoint, ContentBlock, Metadata } from './tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; - -import { newDenoHTTPWorker } from '@valtown/deno-http-worker'; -import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; -import { workerPath } from './code-tool-paths.cjs'; - -/** - * A tool that runs code against a copy of the SDK. - * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, - * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then - * a generic endpoint that can be used to invoke any endpoint with the provided arguments. - * - * @param endpoints - The endpoints to include in the list. - */ -export function codeTool(): Endpoint { - const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; - const tool: Tool = { - name: 'execute', - description: - 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', - inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, - }; - - const handler = async (client: ImageKit, args: unknown) => { - const baseURLHostname = new URL(client.baseURL).hostname; - const { code } = args as { code: string }; - - const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { - runFlags: [ - `--node-modules-dir=manual`, - `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, - `--allow-net=${baseURLHostname}`, - // Allow environment variables because instantiating the client will try to read from them, - // even though they are not set. - '--allow-env', - ], - printOutput: true, - spawnOptions: { - cwd: dirname(workerPath), - }, - }); - - try { - const resp = await new Promise((resolve, reject) => { - worker.addEventListener('exit', (exitCode) => { - reject(new Error(`Worker exited with code ${exitCode}`)); - }); - - const opts: ClientOptions = { - baseURL: client.baseURL, - privateAPIKey: client.privateAPIKey, - password: client.password, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, - }; - - const req = worker.request( - 'http://localhost', - { - headers: { - 'content-type': 'application/json', - }, - method: 'POST', - }, - (resp) => { - const body: Uint8Array[] = []; - resp.on('error', (err) => { - reject(err); - }); - resp.on('data', (chunk) => { - body.push(chunk); - }); - resp.on('end', () => { - resolve( - new Response(Buffer.concat(body).toString(), { - status: resp.statusCode ?? 200, - headers: resp.headers as any, - }), - ); - }); - }, - ); - - const body = JSON.stringify({ - opts, - code, - } satisfies WorkerInput); - - req.write(body, (err) => { - if (err !== null && err !== undefined) { - reject(err); - } - }); - - req.end(); - }); - - if (resp.status === 200) { - const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; - const returnOutput: ContentBlock | null = - result === null ? null - : result === undefined ? null - : { - type: 'text', - text: typeof result === 'string' ? (result as string) : JSON.stringify(result), - }; - const logOutput: ContentBlock | null = - logLines.length === 0 ? - null - : { - type: 'text', - text: logLines.join('\n'), - }; - const errOutput: ContentBlock | null = - errLines.length === 0 ? - null - : { - type: 'text', - text: 'Error output:\n' + errLines.join('\n'), - }; - return { - content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), - }; - } else { - const { message } = (await resp.json()) as WorkerError; - throw new Error(message); - } - } catch (e) { - throw e; - } finally { - worker.terminate(); - } - }; - - return { metadata, tool, handler }; -} diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts deleted file mode 100644 index f84053c7..00000000 --- a/packages/mcp-server/src/compat.ts +++ /dev/null @@ -1,483 +0,0 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { z } from 'zod'; -import { Endpoint } from './tools'; - -export interface ClientCapabilities { - topLevelUnions: boolean; - validJson: boolean; - refs: boolean; - unions: boolean; - formats: boolean; - toolNameLength: number | undefined; -} - -export const defaultClientCapabilities: ClientCapabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, -}; - -export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); -export type ClientType = z.infer; - -// Client presets for compatibility -// Note that these could change over time as models get better, so this is -// a best effort. -export const knownClients: Record, ClientCapabilities> = { - 'openai-agents': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - claude: { - topLevelUnions: true, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - 'claude-code': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - cursor: { - topLevelUnions: false, - validJson: true, - refs: false, - unions: false, - formats: false, - toolNameLength: 50, - }, -}; - -/** - * Attempts to parse strings into JSON objects - */ -export function parseEmbeddedJSON(args: Record, schema: Record) { - let updated = false; - const newArgs: Record = Object.assign({}, args); - - for (const [key, value] of Object.entries(newArgs)) { - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value); - // Only parse if result is a plain object (not array, null, or primitive) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - newArgs[key] = parsed; - updated = true; - } - } catch (e) { - // Not valid JSON, leave as is - } - } - } - - if (updated) { - return newArgs; - } - - return args; -} - -export type JSONSchema = { - type?: string; - properties?: Record; - required?: string[]; - anyOf?: JSONSchema[]; - $ref?: string; - $defs?: Record; - [key: string]: any; -}; - -/** - * Truncates tool names to the specified length while ensuring uniqueness. - * If truncation would cause duplicate names, appends a number to make them unique. - */ -export function truncateToolNames(names: string[], maxLength: number): Map { - if (maxLength <= 0) { - return new Map(); - } - - const renameMap = new Map(); - const usedNames = new Set(); - - const toTruncate = names.filter((name) => name.length > maxLength); - - if (toTruncate.length === 0) { - return renameMap; - } - - const willCollide = - new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; - - if (!willCollide) { - for (const name of toTruncate) { - const truncatedName = name.slice(0, maxLength); - renameMap.set(name, truncatedName); - } - } else { - const baseLength = maxLength - 1; - - for (const name of toTruncate) { - const baseName = name.slice(0, baseLength); - let counter = 1; - - while (usedNames.has(baseName + counter)) { - counter++; - } - - const finalName = baseName + counter; - renameMap.set(name, finalName); - usedNames.add(finalName); - } - } - - return renameMap; -} - -/** - * Removes top-level unions from a tool by splitting it into multiple tools, - * one for each variant in the union. - */ -export function removeTopLevelUnions(tool: Tool): Tool[] { - const inputSchema = tool.inputSchema as JSONSchema; - const variants = inputSchema.anyOf; - - if (!variants || !Array.isArray(variants) || variants.length === 0) { - return [tool]; - } - - const defs = inputSchema.$defs || {}; - - return variants.map((variant, index) => { - const variantSchema: JSONSchema = { - ...inputSchema, - ...variant, - type: 'object', - properties: { - ...(inputSchema.properties || {}), - ...(variant.properties || {}), - }, - }; - - delete variantSchema.anyOf; - - if (!variantSchema['description']) { - variantSchema['description'] = tool.description; - } - - const usedDefs = findUsedDefs(variant, defs); - if (Object.keys(usedDefs).length > 0) { - variantSchema.$defs = usedDefs; - } else { - delete variantSchema.$defs; - } - - return { - ...tool, - name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, - description: variant['description'] || tool.description, - inputSchema: variantSchema, - } as Tool; - }); -} - -function findUsedDefs( - schema: JSONSchema, - defs: Record, - visited: Set = new Set(), -): Record { - const usedDefs: Record = {}; - - if (typeof schema !== 'object' || schema === null) { - return usedDefs; - } - - if (schema.$ref) { - const refParts = schema.$ref.split('/'); - if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { - const defName = refParts[2]; - const def = defs[defName]; - if (def && !visited.has(schema.$ref)) { - usedDefs[defName] = def; - visited.add(schema.$ref); - Object.assign(usedDefs, findUsedDefs(def, defs, visited)); - visited.delete(schema.$ref); - } - } - return usedDefs; - } - - for (const key in schema) { - if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { - Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); - } - } - - return usedDefs; -} - -// Export for testing -export { findUsedDefs }; - -/** - * Inlines all $refs in a schema, eliminating $defs. - * If a circular reference is detected, the circular property is removed. - */ -export function inlineRefs(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - const clonedSchema = { ...schema }; - const defs: Record = schema.$defs || {}; - - delete clonedSchema.$defs; - - const result = inlineRefsRecursive(clonedSchema, defs, new Set()); - // The top level can never be null - return result === null ? {} : result; -} - -function inlineRefsRecursive( - schema: JSONSchema, - defs: Record, - refPath: Set, -): JSONSchema | null { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => { - const processed = inlineRefsRecursive(item, defs, refPath); - return processed === null ? {} : processed; - }) as JSONSchema; - } - - const result = { ...schema }; - - if ('$ref' in result && typeof result.$ref === 'string') { - if (result.$ref.startsWith('#/$defs/')) { - const refName = result.$ref.split('/').pop() as string; - const def = defs[refName]; - - // If we've already seen this ref in our path, we have a circular reference - if (refPath.has(result.$ref)) { - // For circular references, we completely remove the property - // by returning null. The parent will remove it. - return null; - } - - if (def) { - const newRefPath = new Set(refPath); - newRefPath.add(result.$ref); - - const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); - - if (inlinedDef === null) { - return { ...result }; - } - - // Merge the inlined definition with the original schema's properties - // but preserve things like description, etc. - const { $ref, ...rest } = result; - return { ...inlinedDef, ...rest }; - } - } - - // Keep external refs as-is - return result; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); - if (processed === null) { - // Remove properties that would cause circular references - delete result[key]; - } else { - result[key] = processed; - } - } - } - - return result; -} - -/** - * Removes anyOf fields from a schema, using only the first variant. - */ -export function removeAnyOf(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeAnyOf(item)) as JSONSchema; - } - - const result = { ...schema }; - - if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { - const firstVariant = result.anyOf[0]; - - if (firstVariant && typeof firstVariant === 'object') { - // Special handling for properties to ensure deep merge - if (firstVariant.properties && result.properties) { - result.properties = { - ...result.properties, - ...(firstVariant.properties as Record), - }; - } else if (firstVariant.properties) { - result.properties = { ...firstVariant.properties }; - } - - for (const key in firstVariant) { - if (key !== 'properties') { - result[key] = firstVariant[key]; - } - } - } - - delete result.anyOf; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeAnyOf(result[key] as JSONSchema); - } - } - - return result; -} - -/** - * Removes format fields from a schema and appends them to the description. - */ -export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { - if (formatsCapability) { - return schema; - } - - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; - } - - const result = { ...schema }; - - if ('format' in result && typeof result['format'] === 'string') { - const formatStr = `(format: "${result['format']}")`; - - if ('description' in result && typeof result['description'] === 'string') { - result['description'] = `${result['description']} ${formatStr}`; - } else { - result['description'] = formatStr; - } - - delete result['format']; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); - } - } - - return result; -} - -/** - * Applies all compatibility transformations to the endpoints based on the provided capabilities. - */ -export function applyCompatibilityTransformations( - endpoints: Endpoint[], - capabilities: ClientCapabilities, -): Endpoint[] { - let transformedEndpoints = [...endpoints]; - - // Handle top-level unions first as this changes tool names - if (!capabilities.topLevelUnions) { - const newEndpoints: Endpoint[] = []; - - for (const endpoint of transformedEndpoints) { - const variantTools = removeTopLevelUnions(endpoint.tool); - - if (variantTools.length === 1) { - newEndpoints.push(endpoint); - } else { - for (const variantTool of variantTools) { - newEndpoints.push({ - ...endpoint, - tool: variantTool, - }); - } - } - } - - transformedEndpoints = newEndpoints; - } - - if (capabilities.toolNameLength) { - const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); - const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); - - transformedEndpoints = transformedEndpoints.map((endpoint) => ({ - ...endpoint, - tool: { - ...endpoint.tool, - name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, - }, - })); - } - - if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { - transformedEndpoints = transformedEndpoints.map((endpoint) => { - let schema = endpoint.tool.inputSchema as JSONSchema; - - if (!capabilities.refs) { - schema = inlineRefs(schema); - } - - if (!capabilities.unions) { - schema = removeAnyOf(schema); - } - - if (!capabilities.formats) { - schema = removeFormats(schema, capabilities.formats); - } - - return { - ...endpoint, - tool: { - ...endpoint.tool, - inputSchema: schema as typeof endpoint.tool.inputSchema, - }, - }; - }); - } - - return transformedEndpoints; -} - -function toSnakeCase(str: string): string { - return str - .replace(/\s+/g, '_') - .replace(/([a-z])([A-Z])/g, '$1_$2') - .toLowerCase(); -} diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts deleted file mode 100644 index 47d60e0d..00000000 --- a/packages/mcp-server/src/dynamic-tools.ts +++ /dev/null @@ -1,153 +0,0 @@ -import ImageKit from '@imagekit/nodejs'; -import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { z } from 'zod'; -import { Cabidela } from '@cloudflare/cabidela'; - -function zodToInputSchema(schema: z.ZodSchema) { - return { - type: 'object' as const, - ...(zodToJsonSchema(schema) as any), - }; -} - -/** - * A list of tools that expose all the endpoints in the API dynamically. - * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, - * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then - * a generic endpoint that can be used to invoke any endpoint with the provided arguments. - * - * @param endpoints - The endpoints to include in the list. - */ -export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { - const listEndpointsSchema = z.object({ - search_query: z - .string() - .optional() - .describe( - 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', - ), - }); - - const listEndpointsTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'list_api_endpoints', - description: 'List or search for all endpoints in the Image Kit TypeScript API', - inputSchema: zodToInputSchema(listEndpointsSchema), - }, - handler: async (client: ImageKit, args: Record | undefined): Promise => { - const query = args && listEndpointsSchema.parse(args).search_query?.trim(); - - const filteredEndpoints = - query && query.length > 0 ? - endpoints.filter((endpoint) => { - const fieldsToMatch = [ - endpoint.tool.name, - endpoint.tool.description, - endpoint.metadata.resource, - endpoint.metadata.operation, - ...endpoint.metadata.tags, - ]; - return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); - }) - : endpoints; - - return asTextContentResult({ - tools: filteredEndpoints.map(({ tool, metadata }) => ({ - name: tool.name, - description: tool.description, - resource: metadata.resource, - operation: metadata.operation, - tags: metadata.tags, - })), - }); - }, - }; - - const getEndpointSchema = z.object({ - endpoint: z.string().describe('The name of the endpoint to get the schema for.'), - }); - const getEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'get_api_endpoint_schema', - description: - 'Get the schema for an endpoint in the Image Kit TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', - inputSchema: zodToInputSchema(getEndpointSchema), - }, - handler: async (client: ImageKit, args: Record | undefined) => { - if (!args) { - throw new Error('No endpoint provided'); - } - const endpointName = getEndpointSchema.parse(args).endpoint; - - const endpoint = endpoints.find((e) => e.tool.name === endpointName); - if (!endpoint) { - throw new Error(`Endpoint ${endpointName} not found`); - } - return asTextContentResult(endpoint.tool); - }, - }; - - const invokeEndpointSchema = z.object({ - endpoint_name: z.string().describe('The name of the endpoint to invoke.'), - args: z - .record(z.string(), z.any()) - .describe( - 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', - ), - }); - - const invokeEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'write' as const, - tags: [], - }, - tool: { - name: 'invoke_api_endpoint', - description: - 'Invoke an endpoint in the Image Kit TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', - inputSchema: zodToInputSchema(invokeEndpointSchema), - }, - handler: async (client: ImageKit, args: Record | undefined): Promise => { - if (!args) { - throw new Error('No endpoint provided'); - } - const { success, data, error } = invokeEndpointSchema.safeParse(args); - if (!success) { - throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); - } - const { endpoint_name, args: endpointArgs } = data; - - const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); - if (!endpoint) { - throw new Error( - `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, - ); - } - - try { - // Try to validate the arguments for a better error message - const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); - cabidela.validate(endpointArgs); - } catch (error) { - throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); - } - - return await endpoint.handler(client, endpointArgs); - }, - }; - - return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; -} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts deleted file mode 100644 index 1aa9a40c..00000000 --- a/packages/mcp-server/src/filtering.ts +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -import initJq from 'jq-web'; - -export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { - if (jqFilter && typeof jqFilter === 'string') { - return await jq(response, jqFilter); - } else { - return response; - } -} - -async function jq(json: any, jqFilter: string) { - return (await initJq).json(json, jqFilter); -} diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts deleted file mode 100644 index d5162bfa..00000000 --- a/packages/mcp-server/src/headers.ts +++ /dev/null @@ -1,31 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { IncomingMessage } from 'node:http'; -import { ClientOptions } from '@imagekit/nodejs'; - -export const parseAuthHeaders = (req: IncomingMessage): Partial => { - if (req.headers.authorization) { - const scheme = req.headers.authorization.split(' ')[0]!; - const value = req.headers.authorization.slice(scheme.length + 1); - switch (scheme) { - case 'Basic': - const rawValue = Buffer.from(value, 'base64').toString(); - return { - privateAPIKey: rawValue.slice(0, rawValue.search(':')), - password: rawValue.slice(rawValue.search(':') + 1), - }; - default: - throw new Error(`Unsupported authorization scheme`); - } - } - - const privateAPIKey = - Array.isArray(req.headers['x-imagekit-private-api-key']) ? - req.headers['x-imagekit-private-api-key'][0] - : req.headers['x-imagekit-private-api-key']; - const password = - Array.isArray(req.headers['x-org-my-password-token']) ? - req.headers['x-org-my-password-token'][0] - : req.headers['x-org-my-password-token']; - return { privateAPIKey, password }; -}; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts deleted file mode 100644 index c11185b7..00000000 --- a/packages/mcp-server/src/http.ts +++ /dev/null @@ -1,115 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; - -import express from 'express'; -import { fromError } from 'zod-validation-error/v3'; -import { McpOptions, parseQueryOptions } from './options'; -import { initMcpServer, newMcpServer } from './server'; -import { parseAuthHeaders } from './headers'; - -const newServer = ( - defaultMcpOptions: McpOptions, - req: express.Request, - res: express.Response, -): McpServer | null => { - const server = newMcpServer(); - - let mcpOptions: McpOptions; - try { - mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); - } catch (error) { - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: `Invalid request: ${fromError(error)}`, - }, - }); - return null; - } - - try { - const authOptions = parseAuthHeaders(req); - initMcpServer({ - server: server, - clientOptions: { - ...authOptions, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, - }, - mcpOptions, - }); - } catch { - res.status(401).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Unauthorized', - }, - }); - return null; - } - - return server; -}; - -const post = (defaultOptions: McpOptions) => async (req: express.Request, res: express.Response) => { - const server = newServer(defaultOptions, req, res); - // If we return null, we already set the authorization error. - if (server === null) return; - const transport = new StreamableHTTPServerTransport({ - // Stateless server - sessionIdGenerator: undefined, - }); - await server.connect(transport); - await transport.handleRequest(req, res, req.body); -}; - -const get = async (req: express.Request, res: express.Response) => { - res.status(405).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Method not supported', - }, - }); -}; - -const del = async (req: express.Request, res: express.Response) => { - res.status(405).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Method not supported', - }, - }); -}; - -export const streamableHTTPApp = (options: McpOptions): express.Express => { - const app = express(); - app.set('query parser', 'extended'); - app.use(express.json()); - - app.get('/', get); - app.post('/', post(options)); - app.delete('/', del); - - return app; -}; - -export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { - const app = streamableHTTPApp(options); - const server = app.listen(port); - const address = server.address(); - - if (typeof address === 'string') { - console.error(`MCP Server running on streamable HTTP at ${address}`); - } else if (address !== null) { - console.error(`MCP Server running on streamable HTTP on port ${address.port}`); - } else { - console.error(`MCP Server running on streamable HTTP on port ${port}`); - } -}; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts deleted file mode 100644 index c450e4bb..00000000 --- a/packages/mcp-server/src/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env node - -import { selectTools } from './server'; -import { Endpoint, endpoints } from './tools'; -import { McpOptions, parseCLIOptions } from './options'; -import { launchStdioServer } from './stdio'; -import { launchStreamableHTTPServer } from './http'; - -async function main() { - const options = parseOptionsOrError(); - - if (options.list) { - listAllTools(); - return; - } - - const selectedTools = selectToolsOrError(endpoints, options); - - console.error( - `MCP Server starting with ${selectedTools.length} tools:`, - selectedTools.map((e) => e.tool.name), - ); - - switch (options.transport) { - case 'stdio': - await launchStdioServer(options); - break; - case 'http': - await launchStreamableHTTPServer(options, options.port ?? options.socket); - break; - } -} - -if (require.main === module) { - main().catch((error) => { - console.error('Fatal error in main():', error); - process.exit(1); - }); -} - -function parseOptionsOrError() { - try { - return parseCLIOptions(); - } catch (error) { - console.error('Error parsing options:', error); - process.exit(1); - } -} - -function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Endpoint[] { - try { - const includedTools = selectTools(endpoints, options); - if (includedTools.length === 0) { - console.error('No tools match the provided filters.'); - process.exit(1); - } - return includedTools; - } catch (error) { - if (error instanceof Error) { - console.error('Error filtering tools:', error.message); - } else { - console.error('Error filtering tools:', error); - } - process.exit(1); - } -} - -function listAllTools() { - if (endpoints.length === 0) { - console.log('No tools available.'); - return; - } - console.log('Available tools:\n'); - - // Group endpoints by resource - const resourceGroups = new Map(); - - for (const endpoint of endpoints) { - const resource = endpoint.metadata.resource; - if (!resourceGroups.has(resource)) { - resourceGroups.set(resource, []); - } - resourceGroups.get(resource)!.push(endpoint); - } - - // Sort resources alphabetically - const sortedResources = Array.from(resourceGroups.keys()).sort(); - - // Display hierarchically by resource - for (const resource of sortedResources) { - console.log(`Resource: ${resource}`); - - const resourceEndpoints = resourceGroups.get(resource)!; - // Sort endpoints by tool name - resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); - - for (const endpoint of resourceEndpoints) { - const { - tool, - metadata: { operation, tags }, - } = endpoint; - - console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); - console.log(` Description: ${tool.description}`); - } - console.log(''); - } -} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts deleted file mode 100644 index 2100cf58..00000000 --- a/packages/mcp-server/src/options.ts +++ /dev/null @@ -1,456 +0,0 @@ -import qs from 'qs'; -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import z from 'zod'; -import { endpoints, Filter } from './tools'; -import { ClientCapabilities, knownClients, ClientType } from './compat'; - -export type CLIOptions = McpOptions & { - list: boolean; - transport: 'stdio' | 'http'; - port: number | undefined; - socket: string | undefined; -}; - -export type McpOptions = { - client?: ClientType | undefined; - includeDynamicTools?: boolean | undefined; - includeAllTools?: boolean | undefined; - includeCodeTools?: boolean | undefined; - filters?: Filter[] | undefined; - capabilities?: Partial | undefined; -}; - -const CAPABILITY_CHOICES = [ - 'top-level-unions', - 'valid-json', - 'refs', - 'unions', - 'formats', - 'tool-name-length', -] as const; - -type Capability = (typeof CAPABILITY_CHOICES)[number]; - -function parseCapabilityValue(cap: string): { name: Capability; value?: number } { - if (cap.startsWith('tool-name-length=')) { - const parts = cap.split('='); - if (parts.length === 2) { - const length = parseInt(parts[1]!, 10); - if (!isNaN(length)) { - return { name: 'tool-name-length', value: length }; - } - throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); - } - throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); - } - if (!CAPABILITY_CHOICES.includes(cap as Capability)) { - throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); - } - return { name: cap as Capability }; -} - -export function parseCLIOptions(): CLIOptions { - const opts = yargs(hideBin(process.argv)) - .option('tools', { - type: 'string', - array: true, - choices: ['dynamic', 'all', 'code'], - description: 'Use dynamic tools or all tools', - }) - .option('no-tools', { - type: 'string', - array: true, - choices: ['dynamic', 'all', 'code'], - description: 'Do not use any dynamic or all tools', - }) - .option('tool', { - type: 'string', - array: true, - description: 'Include tools matching the specified names', - }) - .option('resource', { - type: 'string', - array: true, - description: 'Include tools matching the specified resources', - }) - .option('operation', { - type: 'string', - array: true, - choices: ['read', 'write'], - description: 'Include tools matching the specified operations', - }) - .option('tag', { - type: 'string', - array: true, - description: 'Include tools with the specified tags', - }) - .option('no-tool', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified names', - }) - .option('no-resource', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified resources', - }) - .option('no-operation', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified operations', - }) - .option('no-tag', { - type: 'string', - array: true, - description: 'Exclude tools with the specified tags', - }) - .option('list', { - type: 'boolean', - description: 'List all tools and exit', - }) - .option('client', { - type: 'string', - choices: Object.keys(knownClients), - description: 'Specify the MCP client being used', - }) - .option('capability', { - type: 'string', - array: true, - description: 'Specify client capabilities', - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('no-capability', { - type: 'string', - array: true, - description: 'Unset client capabilities', - choices: CAPABILITY_CHOICES, - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('describe-capabilities', { - type: 'boolean', - description: 'Print detailed explanation of client capabilities and exit', - }) - .option('transport', { - type: 'string', - choices: ['stdio', 'http'], - default: 'stdio', - description: 'What transport to use; stdio for local servers or http for remote servers', - }) - .option('port', { - type: 'number', - description: 'Port to serve on if using http transport', - }) - .option('socket', { - type: 'string', - description: 'Unix socket to serve on if using http transport', - }) - .help(); - - for (const [command, desc] of examples()) { - opts.example(command, desc); - } - - const argv = opts.parseSync(); - - // Handle describe-capabilities flag - if (argv.describeCapabilities) { - console.log(getCapabilitiesExplanation()); - process.exit(0); - } - - const filters: Filter[] = []; - - // Helper function to support comma-separated values - const splitValues = (values: string[] | undefined): string[] => { - if (!values) return []; - return values.flatMap((v) => v.split(',')); - }; - - for (const tag of splitValues(argv.tag)) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - - for (const tag of splitValues(argv.noTag)) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - - for (const resource of splitValues(argv.resource)) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - - for (const resource of splitValues(argv.noResource)) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - - for (const tool of splitValues(argv.tool)) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - - for (const tool of splitValues(argv.noTool)) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - for (const operation of splitValues(argv.operation)) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - - for (const operation of splitValues(argv.noOperation)) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - - // Parse client capabilities - const clientCapabilities: Partial = {}; - - // Apply individual capability overrides - if (Array.isArray(argv.capability)) { - for (const cap of argv.capability) { - const parsedCap = parseCapabilityValue(cap); - if (parsedCap.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsedCap.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsedCap.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsedCap.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsedCap.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsedCap.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsedCap.value; - } - } - } - - // Handle no-capability options to unset capabilities - if (Array.isArray(argv.noCapability)) { - for (const cap of argv.noCapability) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - } - - const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code') => - explicitTools ? argv.tools?.includes(toolType) && !argv.noTools?.includes(toolType) : undefined; - - const explicitTools = Boolean(argv.tools || argv.noTools); - const includeDynamicTools = shouldIncludeToolType('dynamic'); - const includeAllTools = shouldIncludeToolType('all'); - const includeCodeTools = shouldIncludeToolType('code'); - - const transport = argv.transport as 'stdio' | 'http'; - - const client = argv.client as ClientType; - return { - client: client && client !== 'infer' && knownClients[client] ? client : undefined, - includeDynamicTools, - includeAllTools, - includeCodeTools, - filters, - capabilities: clientCapabilities, - list: argv.list || false, - transport, - port: argv.port, - socket: argv.socket, - }; -} - -const coerceArray = (zodType: T) => - z.preprocess( - (val) => - Array.isArray(val) ? val - : val ? [val] - : val, - z.array(zodType).optional(), - ); - -const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Use dynamic tools or all tools'), - no_tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Do not use dynamic tools or all tools'), - tool: coerceArray(z.string()).describe('Include tools matching the specified names'), - resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), - operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Include tools matching the specified operations', - ), - tag: coerceArray(z.string()).describe('Include tools with the specified tags'), - no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), - no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), - no_operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Exclude tools matching the specified operations', - ), - no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), - client: ClientType.optional().describe('Specify the MCP client being used'), - capability: coerceArray(z.string()).describe('Specify client capabilities'), - no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), -}); - -export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { - const queryObject = typeof query === 'string' ? qs.parse(query) : query; - const queryOptions = QueryOptions.parse(queryObject); - - const filters: Filter[] = [...(defaultOptions.filters ?? [])]; - - for (const resource of queryOptions.resource || []) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - for (const operation of queryOptions.operation || []) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - for (const tag of queryOptions.tag || []) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - for (const tool of queryOptions.tool || []) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - for (const resource of queryOptions.no_resource || []) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - for (const operation of queryOptions.no_operation || []) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - for (const tag of queryOptions.no_tag || []) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - for (const tool of queryOptions.no_tool || []) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - // Parse client capabilities - const clientCapabilities: Partial = { ...defaultOptions.capabilities }; - - for (const cap of queryOptions.capability || []) { - const parsed = parseCapabilityValue(cap); - if (parsed.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsed.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsed.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsed.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsed.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsed.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsed.value; - } - } - - for (const cap of queryOptions.no_capability || []) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - - let dynamicTools: boolean | undefined = - queryOptions.no_tools && !queryOptions.no_tools?.includes('dynamic') ? false - : queryOptions.tools?.includes('dynamic') ? true - : defaultOptions.includeDynamicTools; - - let allTools: boolean | undefined = - queryOptions.no_tools && !queryOptions.no_tools?.includes('all') ? false - : queryOptions.tools?.includes('all') ? true - : defaultOptions.includeAllTools; - - return { - client: queryOptions.client ?? defaultOptions.client, - includeDynamicTools: dynamicTools, - includeAllTools: allTools, - includeCodeTools: undefined, - filters, - capabilities: clientCapabilities, - }; -} - -function getCapabilitiesExplanation(): string { - return ` -Client Capabilities Explanation: - -Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. - -When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. - -Available Capabilities: - -# top-level-unions -Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. - -# refs -Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. - -# valid-json -Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. - -# unions -Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. - -# formats -Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. - -# tool-name-length=N -Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. - -Client Presets (--client): -Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. - -Current presets: -${JSON.stringify(knownClients, null, 2)} - `; -} - -function examples(): [string, string][] { - const firstEndpoint = endpoints[0]!; - const secondEndpoint = - endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; - const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; - const otherEndpoint = secondEndpoint || firstEndpoint; - - return [ - [ - `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, - 'Include tools by name', - ], - [ - `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, - 'Filter by resource and operation', - ], - [ - `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, - 'Use resource wildcards and exclusions', - ], - [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], - [ - `--capability="top-level-unions" --capability="tool-name-length=40"`, - 'Specify individual client capabilities', - ], - [ - `--client="cursor" --no-capability="tool-name-length"`, - 'Use cursor client preset but remove tool name length limit', - ], - ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), - ]; -} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts deleted file mode 100644 index d30dbdd5..00000000 --- a/packages/mcp-server/src/server.ts +++ /dev/null @@ -1,180 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Endpoint, endpoints, HandlerFunction, query } from './tools'; -import { - CallToolRequestSchema, - Implementation, - ListToolsRequestSchema, - Tool, -} from '@modelcontextprotocol/sdk/types.js'; -import { ClientOptions } from '@imagekit/nodejs'; -import ImageKit from '@imagekit/nodejs'; -import { - applyCompatibilityTransformations, - ClientCapabilities, - defaultClientCapabilities, - knownClients, - parseEmbeddedJSON, -} from './compat'; -import { dynamicTools } from './dynamic-tools'; -import { codeTool } from './code-tool'; -import { McpOptions } from './options'; - -export { McpOptions } from './options'; -export { ClientType } from './compat'; -export { Filter } from './tools'; -export { ClientOptions } from '@imagekit/nodejs'; -export { endpoints } from './tools'; - -export const newMcpServer = () => - new McpServer( - { - name: 'imagekit_nodejs_api', - version: '0.0.1-alpha.0', - }, - { capabilities: { tools: {}, logging: {} } }, - ); - -// Create server instance -export const server = newMcpServer(); - -/** - * Initializes the provided MCP Server with the given tools and handlers. - * If not provided, the default client, tools and handlers will be used. - */ -export function initMcpServer(params: { - server: Server | McpServer; - clientOptions?: ClientOptions; - mcpOptions?: McpOptions; -}) { - const server = params.server instanceof McpServer ? params.server.server : params.server; - const mcpOptions = params.mcpOptions ?? {}; - - let providedEndpoints: Endpoint[] | null = null; - let endpointMap: Record | null = null; - - const initTools = (implementation?: Implementation) => { - if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { - mcpOptions.client = - implementation.name.toLowerCase().includes('claude') ? 'claude' - : implementation.name.toLowerCase().includes('cursor') ? 'cursor' - : undefined; - mcpOptions.capabilities = { - ...(mcpOptions.client && knownClients[mcpOptions.client]), - ...mcpOptions.capabilities, - }; - } - providedEndpoints = selectTools(endpoints, mcpOptions); - endpointMap = Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); - }; - - const logAtLevel = - (level: 'debug' | 'info' | 'warning' | 'error') => - (message: string, ...rest: unknown[]) => { - void server.sendLoggingMessage({ - level, - data: { message, rest }, - }); - }; - const logger = { - debug: logAtLevel('debug'), - info: logAtLevel('info'), - warn: logAtLevel('warning'), - error: logAtLevel('error'), - }; - - const client = new ImageKit({ - logger, - ...params.clientOptions, - defaultHeaders: { - ...params.clientOptions?.defaultHeaders, - 'X-Stainless-MCP': 'true', - }, - }); - - server.setRequestHandler(ListToolsRequestSchema, async () => { - if (providedEndpoints === null) { - initTools(server.getClientVersion()); - } - return { - tools: providedEndpoints!.map((endpoint) => endpoint.tool), - }; - }); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (endpointMap === null) { - initTools(server.getClientVersion()); - } - const { name, arguments: args } = request.params; - const endpoint = endpointMap![name]; - if (!endpoint) { - throw new Error(`Unknown tool: ${name}`); - } - - return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); - }); -} - -/** - * Selects the tools to include in the MCP Server based on the provided options. - */ -export function selectTools(endpoints: Endpoint[], options?: McpOptions): Endpoint[] { - const filteredEndpoints = query(options?.filters ?? [], endpoints); - - let includedTools = filteredEndpoints; - - if (includedTools.length > 0) { - if (options?.includeDynamicTools) { - includedTools = dynamicTools(includedTools); - } - } else { - if (options?.includeAllTools) { - includedTools = endpoints; - } else if (options?.includeDynamicTools) { - includedTools = dynamicTools(endpoints); - } else if (options?.includeCodeTools) { - includedTools = [codeTool()]; - } else { - includedTools = endpoints; - } - } - - const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; - return applyCompatibilityTransformations(includedTools, capabilities); -} - -/** - * Runs the provided handler with the given client and arguments. - */ -export async function executeHandler( - tool: Tool, - handler: HandlerFunction, - client: ImageKit, - args: Record | undefined, - compatibilityOptions?: Partial, -) { - const options = { ...defaultClientCapabilities, ...compatibilityOptions }; - if (!options.validJson && args) { - args = parseEmbeddedJSON(args, tool.inputSchema); - } - return await handler(client, args || {}); -} - -export const readEnv = (env: string): string | undefined => { - if (typeof (globalThis as any).process !== 'undefined') { - return (globalThis as any).process.env?.[env]?.trim(); - } else if (typeof (globalThis as any).Deno !== 'undefined') { - return (globalThis as any).Deno.env?.get?.(env)?.trim(); - } - return; -}; - -export const readEnvOrError = (env: string): string => { - let envValue = readEnv(env); - if (envValue === undefined) { - throw new Error(`Environment variable ${env} is not set`); - } - return envValue; -}; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts deleted file mode 100644 index d902a5bb..00000000 --- a/packages/mcp-server/src/stdio.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { initMcpServer, newMcpServer } from './server'; -import { McpOptions } from './options'; - -export const launchStdioServer = async (options: McpOptions) => { - const server = newMcpServer(); - - initMcpServer({ server, mcpOptions: options }); - - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error('MCP Server running on stdio'); -}; diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts deleted file mode 100644 index 7e516de7..00000000 --- a/packages/mcp-server/src/tools.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts deleted file mode 100644 index c189d4a2..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts +++ /dev/null @@ -1,338 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/accounts/origins', - operationId: 'create-origin', -}; - -export const tool: Tool = { - name: 'create_accounts_origins', - description: - '**Note:** This API is currently in beta. \nCreates a new origin and returns the origin object.\n', - inputSchema: { - type: 'object', - properties: { - origin: { - $ref: '#/$defs/origin_request', - }, - }, - required: ['origin'], - $defs: { - origin_request: { - anyOf: [ - { - type: 'object', - title: 'S3', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'S3 Compatible', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - endpoint: { - type: 'string', - description: 'Custom S3-compatible endpoint.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3_COMPATIBLE'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - s3ForcePathStyle: { - type: 'boolean', - description: 'Use path-style S3 URLs?', - }, - }, - required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'Cloudinary Backup', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['CLOUDINARY_BACKUP'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'Web Folder', - properties: { - baseUrl: { - type: 'string', - description: 'Root URL for the web folder origin.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_FOLDER'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - forwardHostHeaderToOrigin: { - type: 'boolean', - description: 'Forward the Host header to origin?', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'name', 'type'], - }, - { - type: 'object', - title: 'Web Proxy', - properties: { - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_PROXY'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['name', 'type'], - }, - { - type: 'object', - title: 'Google Cloud Storage (GCS)', - properties: { - bucket: { - type: 'string', - }, - clientEmail: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - privateKey: { - type: 'string', - }, - type: { - type: 'string', - enum: ['GCS'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], - }, - { - type: 'object', - title: 'Azure Blob Storage', - properties: { - accountName: { - type: 'string', - }, - container: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - sasToken: { - type: 'string', - }, - type: { - type: 'string', - enum: ['AZURE_BLOB'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['accountName', 'container', 'name', 'sasToken', 'type'], - }, - { - type: 'object', - title: 'Akeneo PIM', - properties: { - baseUrl: { - type: 'string', - description: 'Akeneo instance base URL.', - }, - clientId: { - type: 'string', - description: 'Akeneo API client ID.', - }, - clientSecret: { - type: 'string', - description: 'Akeneo API client secret.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - password: { - type: 'string', - description: 'Akeneo API password.', - }, - type: { - type: 'string', - enum: ['AKENEO_PIM'], - }, - username: { - type: 'string', - description: 'Akeneo API username.', - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], - }, - ], - title: 'Origin request', - description: 'Schema for origin request resources.', - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.accounts.origins.create(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts deleted file mode 100644 index d7c62e46..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts +++ /dev/null @@ -1,43 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'delete-origin', -}; - -export const tool: Tool = { - name: 'delete_accounts_origins', - description: - '**Note:** This API is currently in beta. \nPermanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - const response = await client.accounts.origins.delete(id).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts deleted file mode 100644 index a155869e..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts +++ /dev/null @@ -1,41 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'get-origin', -}; - -export const tool: Tool = { - name: 'get_accounts_origins', - description: '**Note:** This API is currently in beta. \nRetrieves the origin identified by `id`.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - return asTextContentResult(await client.accounts.origins.get(id)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts deleted file mode 100644 index 1effce0d..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts +++ /dev/null @@ -1,35 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/origins', - operationId: 'list-origins', -}; - -export const tool: Tool = { - name: 'list_accounts_origins', - description: - '**Note:** This API is currently in beta. \nReturns an array of all configured origins for the current account.\n', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - return asTextContentResult(await client.accounts.origins.list()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts deleted file mode 100644 index a665117f..00000000 --- a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts +++ /dev/null @@ -1,345 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.origins', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/accounts/origins/{id}', - operationId: 'update-origin', -}; - -export const tool: Tool = { - name: 'update_accounts_origins', - description: - '**Note:** This API is currently in beta. \nUpdates the origin identified by `id` and returns the updated origin object.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - origin: { - $ref: '#/$defs/origin_request', - }, - }, - required: ['id', 'origin'], - $defs: { - origin_request: { - anyOf: [ - { - type: 'object', - title: 'S3', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'S3 Compatible', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - endpoint: { - type: 'string', - description: 'Custom S3-compatible endpoint.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['S3_COMPATIBLE'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - s3ForcePathStyle: { - type: 'boolean', - description: 'Use path-style S3 URLs?', - }, - }, - required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'Cloudinary Backup', - properties: { - accessKey: { - type: 'string', - description: 'Access key for the bucket.', - }, - bucket: { - type: 'string', - description: 'S3 bucket name.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - secretKey: { - type: 'string', - description: 'Secret key for the bucket.', - }, - type: { - type: 'string', - enum: ['CLOUDINARY_BACKUP'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - description: 'Path prefix inside the bucket.', - }, - }, - required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], - }, - { - type: 'object', - title: 'Web Folder', - properties: { - baseUrl: { - type: 'string', - description: 'Root URL for the web folder origin.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_FOLDER'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - forwardHostHeaderToOrigin: { - type: 'boolean', - description: 'Forward the Host header to origin?', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'name', 'type'], - }, - { - type: 'object', - title: 'Web Proxy', - properties: { - name: { - type: 'string', - description: 'Display name of the origin.', - }, - type: { - type: 'string', - enum: ['WEB_PROXY'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['name', 'type'], - }, - { - type: 'object', - title: 'Google Cloud Storage (GCS)', - properties: { - bucket: { - type: 'string', - }, - clientEmail: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - privateKey: { - type: 'string', - }, - type: { - type: 'string', - enum: ['GCS'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], - }, - { - type: 'object', - title: 'Azure Blob Storage', - properties: { - accountName: { - type: 'string', - }, - container: { - type: 'string', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - sasToken: { - type: 'string', - }, - type: { - type: 'string', - enum: ['AZURE_BLOB'], - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - prefix: { - type: 'string', - }, - }, - required: ['accountName', 'container', 'name', 'sasToken', 'type'], - }, - { - type: 'object', - title: 'Akeneo PIM', - properties: { - baseUrl: { - type: 'string', - description: 'Akeneo instance base URL.', - }, - clientId: { - type: 'string', - description: 'Akeneo API client ID.', - }, - clientSecret: { - type: 'string', - description: 'Akeneo API client secret.', - }, - name: { - type: 'string', - description: 'Display name of the origin.', - }, - password: { - type: 'string', - description: 'Akeneo API password.', - }, - type: { - type: 'string', - enum: ['AKENEO_PIM'], - }, - username: { - type: 'string', - description: 'Akeneo API username.', - }, - baseUrlForCanonicalHeader: { - type: 'string', - description: 'URL used in the Canonical header (if enabled).', - }, - includeCanonicalHeader: { - type: 'boolean', - description: 'Whether to send a Canonical header.', - }, - }, - required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], - }, - ], - title: 'Origin request', - description: 'Schema for origin request resources.', - }, - }, - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - return asTextContentResult(await client.accounts.origins.update(id, body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts deleted file mode 100644 index cc9742bb..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts +++ /dev/null @@ -1,103 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/accounts/url-endpoints', - operationId: 'create-url-endpoint', -}; - -export const tool: Tool = { - name: 'create_accounts_url_endpoints', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nCreates a new URL‑endpoint and returns the resulting object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - description: { - type: 'string', - description: 'Description of the URL endpoint.', - }, - origins: { - type: 'array', - description: - 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', - items: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - urlPrefix: { - type: 'string', - description: - 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', - }, - urlRewriter: { - anyOf: [ - { - type: 'object', - title: 'Cloudinary URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['CLOUDINARY'], - }, - preserveAssetDeliveryTypes: { - type: 'boolean', - description: 'Whether to preserve `/` in the rewritten URL.', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Imgix URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['IMGIX'], - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Akamai URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['AKAMAI'], - }, - }, - required: ['type'], - }, - ], - description: 'Configuration for third-party URL rewriting.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['description'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts deleted file mode 100644 index c323a3c9..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts +++ /dev/null @@ -1,43 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'delete-url-endpoint', -}; - -export const tool: Tool = { - name: 'delete_accounts_url_endpoints', - description: - '**Note:** This API is currently in beta. \nDeletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation.\n', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, ...body } = args as any; - const response = await client.accounts.urlEndpoints.delete(id).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts deleted file mode 100644 index 98d9bba7..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts +++ /dev/null @@ -1,49 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'get-url-endpoint', -}; - -export const tool: Tool = { - name: 'get_accounts_url_endpoints', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nRetrieves the URL‑endpoint identified by `id`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts deleted file mode 100644 index 3d27514a..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts +++ /dev/null @@ -1,44 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/url-endpoints', - operationId: 'list-url-endpoints', -}; - -export const tool: Tool = { - name: 'list_accounts_url_endpoints', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n },\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts deleted file mode 100644 index ec8140dc..00000000 --- a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts +++ /dev/null @@ -1,112 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.urlEndpoints', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/accounts/url-endpoints/{id}', - operationId: 'update-url-endpoint', -}; - -export const tool: Tool = { - name: 'update_accounts_url_endpoints', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nUpdates the URL‑endpoint identified by `id` and returns the updated object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: - 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', - }, - description: { - type: 'string', - description: 'Description of the URL endpoint.', - }, - origins: { - type: 'array', - description: - 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', - items: { - type: 'string', - description: - 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', - }, - }, - urlPrefix: { - type: 'string', - description: - 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', - }, - urlRewriter: { - anyOf: [ - { - type: 'object', - title: 'Cloudinary URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['CLOUDINARY'], - }, - preserveAssetDeliveryTypes: { - type: 'boolean', - description: 'Whether to preserve `/` in the rewritten URL.', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Imgix URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['IMGIX'], - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Akamai URL Rewriter', - properties: { - type: { - type: 'string', - enum: ['AKAMAI'], - }, - }, - required: ['type'], - }, - ], - description: 'Configuration for third-party URL rewriting.', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'description'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), - ); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts deleted file mode 100644 index 2035c6a5..00000000 --- a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'accounts.usage', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/accounts/usage', - operationId: 'get-usage', -}; - -export const tool: Tool = { - name: 'get_accounts_usage', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - endDate: { - type: 'string', - description: - 'Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days.', - format: 'date', - }, - startDate: { - type: 'string', - description: - 'Specify a `startDate` in `YYYY-MM-DD` format. It should be before the `endDate`. The difference between `startDate` and `endDate` should be less than 90 days.', - format: 'date', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['endDate', 'startDate'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts deleted file mode 100644 index 2cc85df2..00000000 --- a/packages/mcp-server/src/tools/assets/list-assets.ts +++ /dev/null @@ -1,94 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'assets', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files', - operationId: 'list-and-search-assets', -}; - -export const tool: Tool = { - name: 'list_assets', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileType: { - type: 'string', - description: - 'Filter results by file type.\n\n- `all` — include all file types \n- `image` — include only image files \n- `non-image` — include only non-image files (e.g., JS, CSS, video)', - enum: ['all', 'image', 'non-image'], - }, - limit: { - type: 'integer', - description: 'The maximum number of results to return in response.\n', - }, - path: { - type: 'string', - description: - 'Folder path if you want to limit the search within a specific folder. For example, `/sales-banner/` will only search in folder sales-banner.\n\nNote : If your use case involves searching within a folder as well as its subfolders, you can use `path` parameter in `searchQuery` with appropriate operator.\nCheckout [Supported parameters](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#supported-parameters) for more information.\n', - }, - searchQuery: { - type: 'string', - description: - 'Query string in a Lucene-like query language e.g. `createdAt > "7d"`.\n\nNote : When the searchQuery parameter is present, the following query parameters will have no effect on the result:\n\n1. `tags`\n2. `type`\n3. `name`\n\n[Learn more](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#advanced-search-queries) from examples.\n', - }, - skip: { - type: 'integer', - description: 'The number of results to skip before returning results.\n', - }, - sort: { - type: 'string', - description: 'Sort the results by one of the supported fields in ascending or descending order.', - enum: [ - 'ASC_NAME', - 'DESC_NAME', - 'ASC_CREATED', - 'DESC_CREATED', - 'ASC_UPDATED', - 'DESC_UPDATED', - 'ASC_HEIGHT', - 'DESC_HEIGHT', - 'ASC_WIDTH', - 'DESC_WIDTH', - 'ASC_SIZE', - 'DESC_SIZE', - 'ASC_RELEVANCE', - 'DESC_RELEVANCE', - ], - }, - type: { - type: 'string', - description: - 'Filter results by asset type.\n\n- `file` — returns only files \n- `file-version` — returns specific file versions \n- `folder` — returns only folders \n- `all` — returns both files and folders (excludes `file-version`)', - enum: ['file', 'file-version', 'folder', 'all'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts deleted file mode 100644 index acac59c2..00000000 --- a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts +++ /dev/null @@ -1,309 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'beta.v2.files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/api/v2/files/upload', - operationId: 'upload-file-v2', -}; - -export const tool: Tool = { - name: 'upload_v2_beta_files', - description: - 'The V2 API enhances security by verifying the entire payload using JWT. This API is in beta.\n\nImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', - inputSchema: { - type: 'object', - properties: { - file: { - type: 'string', - description: - 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', - }, - fileName: { - type: 'string', - description: 'The name with which the file has to be uploaded.\n', - }, - token: { - type: 'string', - description: - "This is the client-generated JSON Web Token (JWT). The ImageKit.io server uses it to authenticate and check that the upload request parameters have not been tampered with after the token has been generated. Learn how to create the token on the page below. This field is only required for authentication when uploading a file from the client side.\n\n\n**Note**: Sending a JWT that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new token.\n\n\n**⚠️Warning**: JWT must be generated on the server-side because it is generated using your account's private API key. This field is required for authentication when uploading a file from the client-side.\n", - }, - checks: { - type: 'string', - description: - 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file-v2#upload-api-checks).\n', - }, - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', - }, - customMetadata: { - type: 'object', - description: - 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - extensions: { - type: 'array', - description: - 'Array of extensions to be applied to the image. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - folder: { - type: 'string', - description: - "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. Using multiple `/` creates a nested folder.\n", - }, - isPrivateFile: { - type: 'boolean', - description: - 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', - }, - isPublished: { - type: 'boolean', - description: - 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', - }, - overwriteAITags: { - type: 'boolean', - description: - 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', - }, - overwriteCustomMetadata: { - type: 'boolean', - description: - 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', - }, - overwriteFile: { - type: 'boolean', - description: - 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', - }, - overwriteTags: { - type: 'boolean', - description: - 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', - }, - responseFields: { - type: 'array', - description: 'Array of response field keys to include in the API response body.\n', - items: { - type: 'string', - enum: [ - 'tags', - 'customCoordinates', - 'isPrivateFile', - 'embeddedMetadata', - 'isPublished', - 'customMetadata', - 'metadata', - ], - }, - }, - tags: { - type: 'array', - description: - 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', - items: { - type: 'string', - }, - }, - transformation: { - type: 'object', - description: - "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", - properties: { - post: { - type: 'array', - description: - 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Simple post-transformation', - properties: { - type: { - type: 'string', - description: 'Transformation type.', - enum: ['transformation'], - }, - value: { - type: 'string', - description: - 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', - }, - }, - required: ['type', 'value'], - }, - { - type: 'object', - title: 'Convert GIF to video', - properties: { - type: { - type: 'string', - description: 'Converts an animated GIF into an MP4.', - enum: ['gif-to-video'], - }, - value: { - type: 'string', - description: - 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Generate a thumbnail', - properties: { - type: { - type: 'string', - description: 'Generates a thumbnail image.', - enum: ['thumbnail'], - }, - value: { - type: 'string', - description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Adaptive Bitrate Streaming', - properties: { - protocol: { - type: 'string', - description: 'Streaming protocol to use (`hls` or `dash`).', - enum: ['hls', 'dash'], - }, - type: { - type: 'string', - description: 'Adaptive Bitrate Streaming (ABS) setup.', - enum: ['abs'], - }, - value: { - type: 'string', - description: - 'List of different representations you want to create separated by an underscore.\n', - }, - }, - required: ['protocol', 'type', 'value'], - }, - ], - }, - }, - pre: { - type: 'string', - description: - 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', - }, - }, - }, - useUniqueFileName: { - type: 'boolean', - description: - 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - required: ['file', 'fileName'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.beta.v2.files.upload(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts deleted file mode 100644 index 1bccd5a1..00000000 --- a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts +++ /dev/null @@ -1,46 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'cache.invalidation', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/purge', - operationId: 'purge-cache', -}; - -export const tool: Tool = { - name: 'create_cache_invalidation', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - url: { - type: 'string', - description: 'The full URL of the file to be purged.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['url'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts deleted file mode 100644 index 4ef295b3..00000000 --- a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'cache.invalidation', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/purge/{requestId}', - operationId: 'purge-status', -}; - -export const tool: Tool = { - name: 'get_cache_invalidation', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - requestId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['requestId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { requestId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts deleted file mode 100644 index a55ee9ae..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts +++ /dev/null @@ -1,154 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/customMetadataFields', - operationId: 'create-new-field', -}; - -export const tool: Tool = { - name: 'create_custom_metadata_fields', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - label: { - type: 'string', - description: - 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI.', - }, - name: { - type: 'string', - description: - 'API name of the custom metadata field. This should be unique across all (including deleted) custom metadata fields.', - }, - schema: { - type: 'object', - properties: { - type: { - type: 'string', - description: 'Type of the custom metadata field.', - enum: ['Text', 'Textarea', 'Number', 'Date', 'Boolean', 'SingleSelect', 'MultiSelect'], - }, - defaultValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - { - type: 'array', - title: 'Mixed', - description: - 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - ], - description: - 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', - }, - isValueRequired: { - type: 'boolean', - description: - 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', - }, - maxLength: { - type: 'number', - description: - 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - maxValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - minLength: { - type: 'number', - description: - 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - minValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - selectOptions: { - type: 'array', - description: - 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - }, - required: ['type'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['label', 'name', 'schema'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts deleted file mode 100644 index bb208216..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/customMetadataFields/{id}', - operationId: 'delete-a-field', -}; - -export const tool: Tool = { - name: 'delete_custom_metadata_fields', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts deleted file mode 100644 index b9d23289..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts +++ /dev/null @@ -1,48 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/customMetadataFields', - operationId: 'list-all-fields', -}; - -export const tool: Tool = { - name: 'list_custom_metadata_fields', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n },\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - includeDeleted: { - type: 'boolean', - description: 'Set it to `true` to include deleted field objects in the API response.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts deleted file mode 100644 index ae7291fb..00000000 --- a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts +++ /dev/null @@ -1,150 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'customMetadataFields', - operation: 'write', - tags: [], - httpMethod: 'patch', - httpPath: '/v1/customMetadataFields/{id}', - operationId: 'update-existing-field', -}; - -export const tool: Tool = { - name: 'update_custom_metadata_fields', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API updates the label or schema of an existing custom metadata field.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - label: { - type: 'string', - description: - 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI. This parameter is required if `schema` is not provided.', - }, - schema: { - type: 'object', - description: - 'An object that describes the rules for the custom metadata key. This parameter is required if `label` is not provided. Note: `type` cannot be updated and will be ignored if sent with the `schema`. The schema will be validated as per the existing `type`.\n', - properties: { - defaultValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - { - type: 'array', - title: 'Mixed', - description: - 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - ], - description: - 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', - }, - isValueRequired: { - type: 'boolean', - description: - 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', - }, - maxLength: { - type: 'number', - description: - 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - maxValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - minLength: { - type: 'number', - description: - 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', - }, - minValue: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - description: - 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', - }, - selectOptions: { - type: 'array', - description: - 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), - ); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts deleted file mode 100644 index f6a051c2..00000000 --- a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/addTags', - operationId: 'add-tags-bulk', -}; - -export const tool: Tool = { - name: 'add_tags_files_bulk', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds to which you want to add tags.\n', - items: { - type: 'string', - }, - }, - tags: { - type: 'array', - description: 'An array of tags that you want to add to the files.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds', 'tags'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts deleted file mode 100644 index b41409b3..00000000 --- a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts +++ /dev/null @@ -1,49 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/batch/deleteByFileIds', - operationId: 'delete-multiple-files', -}; - -export const tool: Tool = { - name: 'delete_files_bulk', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds which you want to delete.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts deleted file mode 100644 index f54f024a..00000000 --- a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/removeAITags', - operationId: 'remove-ai-tags-bulk', -}; - -export const tool: Tool = { - name: 'remove_ai_tags_files_bulk', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - AITags: { - type: 'array', - description: 'An array of AITags that you want to remove from the files.\n', - items: { - type: 'string', - }, - }, - fileIds: { - type: 'array', - description: 'An array of fileIds from which you want to remove AITags.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['AITags', 'fileIds'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts deleted file mode 100644 index d51184d2..00000000 --- a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.bulk', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/removeTags', - operationId: 'remove-tags-bulk', -}; - -export const tool: Tool = { - name: 'remove_tags_files_bulk', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileIds: { - type: 'array', - description: 'An array of fileIds from which you want to remove tags.\n', - items: { - type: 'string', - }, - }, - tags: { - type: 'array', - description: 'An array of tags that you want to remove from the files.\n', - items: { - type: 'string', - }, - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileIds', 'tags'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts deleted file mode 100644 index d7002a58..00000000 --- a/packages/mcp-server/src/tools/files/copy-files.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/copy', - operationId: 'copy-file', -}; - -export const tool: Tool = { - name: 'copy_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the folder you want to copy the above file into.\n', - }, - sourceFilePath: { - type: 'string', - description: 'The full path of the file you want to copy.\n', - }, - includeFileVersions: { - type: 'boolean', - description: - 'Option to copy all versions of a file. By default, only the current version of the file is copied. When set to true, all versions of the file will be copied. Default value - `false`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFilePath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts deleted file mode 100644 index 5cc7e0a8..00000000 --- a/packages/mcp-server/src/tools/files/delete-files.ts +++ /dev/null @@ -1,41 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/files/{fileId}', - operationId: 'delete-file', -}; - -export const tool: Tool = { - name: 'delete_files', - description: - 'This API deletes the file and all its file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n', - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - }, - required: ['fileId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, ...body } = args as any; - const response = await client.files.delete(fileId).asResponse(); - return asTextContentResult(await response.text()); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts deleted file mode 100644 index d7e69593..00000000 --- a/packages/mcp-server/src/tools/files/get-files.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/details', - operationId: 'get-file-details', -}; - -export const tool: Tool = { - name: 'get_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes about the current version of the file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts deleted file mode 100644 index f6ce3f07..00000000 --- a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.metadata', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/metadata', - operationId: 'get-uploaded-file-metadata', -}; - -export const tool: Tool = { - name: 'get_files_metadata', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API.\n\nYou can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts deleted file mode 100644 index b3758ad9..00000000 --- a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts +++ /dev/null @@ -1,48 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.metadata', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/metadata', - operationId: 'get-metadata-from-url', -}; - -export const tool: Tool = { - name: 'get_from_url_files_metadata', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - url: { - type: 'string', - description: 'Should be a valid file URL. It should be accessible using your ImageKit.io account.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['url'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts deleted file mode 100644 index a4b07ec4..00000000 --- a/packages/mcp-server/src/tools/files/move-files.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/files/move', - operationId: 'move-file', -}; - -export const tool: Tool = { - name: 'move_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the folder you want to move the above file into.\n', - }, - sourceFilePath: { - type: 'string', - description: 'The full path of the file you want to move.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFilePath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts deleted file mode 100644 index 16d682c6..00000000 --- a/packages/mcp-server/src/tools/files/rename-files.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/files/rename', - operationId: 'rename-file', -}; - -export const tool: Tool = { - name: 'rename_files', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - filePath: { - type: 'string', - description: 'The full path of the file you want to rename.\n', - }, - newFileName: { - type: 'string', - description: - 'The new name of the file. A filename can contain:\n\nAlphanumeric Characters: `a-z`, `A-Z`, `0-9` (including Unicode letters, marks, and numerals in other languages).\nSpecial Characters: `.`, `_`, and `-`.\n\nAny other character, including space, will be replaced by `_`.\n', - }, - purgeCache: { - type: 'boolean', - description: - "Option to purge cache for the old file and its versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove cached content of old file and its versions. This purge request is counted against your monthly purge quota.\n\nNote: If the old file were accessible at `https://ik.imagekit.io/demo/old-filename.jpg`, a purge cache request would be issued against `https://ik.imagekit.io/demo/old-filename.jpg*` (with a wildcard at the end). It will remove the file and its versions' URLs and any transformations made using query parameters on this file or its versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\n\n\nDefault value - `false`\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['filePath', 'newFileName'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/update-files.ts b/packages/mcp-server/src/tools/files/update-files.ts deleted file mode 100644 index bcd87a73..00000000 --- a/packages/mcp-server/src/tools/files/update-files.ts +++ /dev/null @@ -1,192 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'patch', - httpPath: '/v1/files/{fileId}/details', - operationId: 'update-file-details', -}; - -export const tool: Tool = { - name: 'update_files', - description: - 'This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API.\n', - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - update: { - anyOf: [ - { - type: 'object', - title: 'Update file details', - properties: { - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image in the format `x,y,width,height` e.g. `10,10,100,100`. Send `null` to unset this value.\n', - }, - customMetadata: { - type: 'object', - description: - 'A key-value data to be associated with the asset. To unset a key, send `null` value for that key. Before setting any custom metadata on an asset you have to create the field using custom metadata fields API.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - extensions: { - type: 'array', - description: - 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - removeAITags: { - anyOf: [ - { - type: 'array', - items: { - type: 'string', - }, - }, - { - type: 'string', - enum: ['all'], - }, - ], - description: - 'An array of AITags associated with the file that you want to remove, e.g. `["car", "vehicle", "motorsports"]`. \n\nIf you want to remove all AITags associated with the file, send a string - "all".\n\nNote: The remove operation for `AITags` executes before any of the `extensions` are processed.\n', - }, - tags: { - type: 'array', - description: - 'An array of tags associated with the file, such as `["tag1", "tag2"]`. Send `null` to unset all tags associated with the file.\n', - items: { - type: 'string', - }, - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - }, - { - type: 'object', - title: 'Change publication status', - properties: { - publish: { - type: 'object', - description: 'Configure the publication status of a file and its versions.\n', - properties: { - isPublished: { - type: 'boolean', - description: 'Set to `true` to publish the file. Set to `false` to unpublish the file.\n', - }, - includeFileVersions: { - type: 'boolean', - description: - 'Set to `true` to publish/unpublish all versions of the file. Set to `false` to publish/unpublish only the current version of the file.\n', - }, - }, - required: ['isPublished'], - }, - }, - }, - ], - }, - }, - required: ['fileId'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, ...body } = args as any; - return asTextContentResult(await client.files.update(fileId, body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts deleted file mode 100644 index 6479106d..00000000 --- a/packages/mcp-server/src/tools/files/upload-files.ts +++ /dev/null @@ -1,325 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/api/v1/files/upload', - operationId: 'upload-file', -}; - -export const tool: Tool = { - name: 'upload_files', - description: - 'ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload.\n\nThe [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', - inputSchema: { - type: 'object', - properties: { - file: { - type: 'string', - description: - 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', - }, - fileName: { - type: 'string', - description: - 'The name with which the file has to be uploaded.\nThe file name can contain:\n\n - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`.\n - Special Characters: `.`, `-`\n\nAny other character including space will be replaced by `_`\n', - }, - token: { - type: 'string', - description: - 'A unique value that the ImageKit.io server will use to recognize and prevent subsequent retries for the same request. We suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. This field is only required for authentication when uploading a file from the client side.\n\n**Note**: Sending a value that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new value for this field.\n', - }, - checks: { - type: 'string', - description: - 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks).\n', - }, - customCoordinates: { - type: 'string', - description: - 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', - }, - customMetadata: { - type: 'object', - description: - 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', - additionalProperties: true, - }, - description: { - type: 'string', - description: 'Optional text to describe the contents of the file.\n', - }, - expire: { - type: 'integer', - description: - 'The time until your signature is valid. It must be a [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into the future. It should be in seconds. This field is only required for authentication when uploading a file from the client side.\n', - }, - extensions: { - type: 'array', - description: - 'Array of extensions to be applied to the image. Each extension can be configured with specific parameters based on the extension type.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Remove background', - properties: { - name: { - type: 'string', - description: 'Specifies the background removal extension.', - enum: ['remove-bg'], - }, - options: { - type: 'object', - properties: { - add_shadow: { - type: 'boolean', - description: - 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', - }, - bg_color: { - type: 'string', - description: - 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', - }, - bg_image_url: { - type: 'string', - description: - 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', - }, - semitransparency: { - type: 'boolean', - description: - 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', - }, - }, - }, - }, - required: ['name'], - }, - { - type: 'object', - title: 'Auto tagging', - properties: { - maxTags: { - type: 'integer', - description: 'Maximum number of tags to attach to the asset.', - }, - minConfidence: { - type: 'integer', - description: 'Minimum confidence level for tags to be considered valid.', - }, - name: { - type: 'string', - description: 'Specifies the auto-tagging extension used.', - enum: ['google-auto-tagging', 'aws-auto-tagging'], - }, - }, - required: ['maxTags', 'minConfidence', 'name'], - }, - { - type: 'object', - title: 'Auto description', - properties: { - name: { - type: 'string', - description: 'Specifies the auto description extension.', - enum: ['ai-auto-description'], - }, - }, - required: ['name'], - }, - ], - }, - }, - folder: { - type: 'string', - description: - "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created.\n\nThe folder name can contain:\n\n - Alphanumeric Characters: `a-z` , `A-Z` , `0-9`\n - Special Characters: `/` , `_` , `-`\n\nUsing multiple `/` creates a nested folder.\n", - }, - isPrivateFile: { - type: 'boolean', - description: - 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', - }, - isPublished: { - type: 'boolean', - description: - 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', - }, - overwriteAITags: { - type: 'boolean', - description: - 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', - }, - overwriteCustomMetadata: { - type: 'boolean', - description: - 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', - }, - overwriteFile: { - type: 'boolean', - description: - 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', - }, - overwriteTags: { - type: 'boolean', - description: - 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', - }, - publicKey: { - type: 'string', - description: - 'Your ImageKit.io public key. This field is only required for authentication when uploading a file from the client side.\n', - }, - responseFields: { - type: 'array', - description: 'Array of response field keys to include in the API response body.\n', - items: { - type: 'string', - enum: [ - 'tags', - 'customCoordinates', - 'isPrivateFile', - 'embeddedMetadata', - 'isPublished', - 'customMetadata', - 'metadata', - ], - }, - }, - signature: { - type: 'string', - description: - 'HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a key. Learn how to create a signature on the page below. This should be in lowercase.\n\nSignature must be calculated on the server-side. This field is only required for authentication when uploading a file from the client side.\n', - }, - tags: { - type: 'array', - description: - 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', - items: { - type: 'string', - }, - }, - transformation: { - type: 'object', - description: - "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", - properties: { - post: { - type: 'array', - description: - 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', - items: { - anyOf: [ - { - type: 'object', - title: 'Simple post-transformation', - properties: { - type: { - type: 'string', - description: 'Transformation type.', - enum: ['transformation'], - }, - value: { - type: 'string', - description: - 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', - }, - }, - required: ['type', 'value'], - }, - { - type: 'object', - title: 'Convert GIF to video', - properties: { - type: { - type: 'string', - description: 'Converts an animated GIF into an MP4.', - enum: ['gif-to-video'], - }, - value: { - type: 'string', - description: - 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Generate a thumbnail', - properties: { - type: { - type: 'string', - description: 'Generates a thumbnail image.', - enum: ['thumbnail'], - }, - value: { - type: 'string', - description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', - }, - }, - required: ['type'], - }, - { - type: 'object', - title: 'Adaptive Bitrate Streaming', - properties: { - protocol: { - type: 'string', - description: 'Streaming protocol to use (`hls` or `dash`).', - enum: ['hls', 'dash'], - }, - type: { - type: 'string', - description: 'Adaptive Bitrate Streaming (ABS) setup.', - enum: ['abs'], - }, - value: { - type: 'string', - description: - 'List of different representations you want to create separated by an underscore.\n', - }, - }, - required: ['protocol', 'type', 'value'], - }, - ], - }, - }, - pre: { - type: 'string', - description: - 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', - }, - }, - }, - useUniqueFileName: { - type: 'boolean', - description: - 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', - }, - webhookUrl: { - type: 'string', - description: - 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', - }, - }, - required: ['file', 'fileName'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.files.upload(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts deleted file mode 100644 index 5688f6ad..00000000 --- a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/files/{fileId}/versions/{versionId}', - operationId: 'delete-file-version', -}; - -export const tool: Tool = { - name: 'delete_files_versions', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), - ); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts deleted file mode 100644 index ecb444c8..00000000 --- a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/versions/{versionId}', - operationId: 'get-file-version-details', -}; - -export const tool: Tool = { - name: 'get_files_versions', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes of a file version.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.get(versionId, body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts deleted file mode 100644 index 94765bdc..00000000 --- a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/files/{fileId}/versions', - operationId: 'list-file-versions', -}; - -export const tool: Tool = { - name: 'list_files_versions', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { fileId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts deleted file mode 100644 index 177dfc8f..00000000 --- a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'files.versions', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/v1/files/{fileId}/versions/{versionId}/restore', - operationId: 'restore-file-version', -}; - -export const tool: Tool = { - name: 'restore_files_versions', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API restores a file version as the current file version.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - fileId: { - type: 'string', - }, - versionId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['fileId', 'versionId'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { versionId, jq_filter, ...body } = args as any; - return asTextContentResult( - await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), - ); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts deleted file mode 100644 index 6a748982..00000000 --- a/packages/mcp-server/src/tools/folders/copy-folders.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/copyFolder', - operationId: 'copy-folder', -}; - -export const tool: Tool = { - name: 'copy_folders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the destination folder where you want to copy the source folder into.\n', - }, - sourceFolderPath: { - type: 'string', - description: 'The full path to the source folder you want to copy.\n', - }, - includeVersions: { - type: 'boolean', - description: - 'Option to copy all versions of files that are nested inside the selected folder. By default, only the current version of each file will be copied. When set to true, all versions of each file will be copied. Default value - `false`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts deleted file mode 100644 index 01823636..00000000 --- a/packages/mcp-server/src/tools/folders/create-folders.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/folder', - operationId: 'create-folder', -}; - -export const tool: Tool = { - name: 'create_folders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderName: { - type: 'string', - description: - 'The folder will be created with this name. \n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) will be replaced by an underscore i.e. `_`.\n', - }, - parentFolderPath: { - type: 'string', - description: - "The folder where the new folder should be created, for root use `/` else the path e.g. `containing/folder/`.\n\nNote: If any folder(s) is not present in the parentFolderPath parameter, it will be automatically created. For example, if you pass `/product/images/summer`, then `product`, `images`, and `summer` folders will be created if they don't already exist.\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderName', 'parentFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts deleted file mode 100644 index ddd2fdd7..00000000 --- a/packages/mcp-server/src/tools/folders/delete-folders.ts +++ /dev/null @@ -1,48 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/v1/folder', - operationId: 'delete-folder', -}; - -export const tool: Tool = { - name: 'delete_folders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderPath: { - type: 'string', - description: 'Full path to the folder you want to delete. For example `/folder/to/delete/`.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderPath'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts deleted file mode 100644 index 51a6af24..00000000 --- a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders.job', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/v1/bulkJobs/{jobId}', - operationId: 'bulk-job-status', -}; - -export const tool: Tool = { - name: 'get_folders_job', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jobId: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['jobId'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jobId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts deleted file mode 100644 index 0d035986..00000000 --- a/packages/mcp-server/src/tools/folders/move-folders.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/moveFolder', - operationId: 'move-folder', -}; - -export const tool: Tool = { - name: 'move_folders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", - inputSchema: { - type: 'object', - properties: { - destinationPath: { - type: 'string', - description: 'Full path to the destination folder where you want to move the source folder into.\n', - }, - sourceFolderPath: { - type: 'string', - description: 'The full path to the source folder you want to move.\n', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['destinationPath', 'sourceFolderPath'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts deleted file mode 100644 index 3640cb2e..00000000 --- a/packages/mcp-server/src/tools/folders/rename-folders.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { maybeFilter } from '@imagekit/nodejs-mcp/filtering'; -import { Metadata, asTextContentResult } from '@imagekit/nodejs-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import ImageKit from '@imagekit/nodejs'; - -export const metadata: Metadata = { - resource: 'folders', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/v1/bulkJobs/renameFolder', - operationId: 'rename-folder', -}; - -export const tool: Tool = { - name: 'rename_folders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", - inputSchema: { - type: 'object', - properties: { - folderPath: { - type: 'string', - description: 'The full path to the folder you want to rename.\n', - }, - newFolderName: { - type: 'string', - description: - 'The new name for the folder.\n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) and `-` will be replaced by an underscore i.e. `_`.\n', - }, - purgeCache: { - type: 'boolean', - description: - "Option to purge cache for the old nested files and their versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove the cached content of the old nested files and their versions. There will only be one purge request for all the nested files, which will be counted against your monthly purge quota.\n\nNote: A purge cache request will be issued against `https://ik.imagekit.io/old/folder/path*` (with a wildcard at the end). This will remove all nested files, their versions' URLs, and any transformations made using query parameters on these files or their versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\nDefault value - `false`\n", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['folderPath', 'newFolderName'], - }, - annotations: {}, -}; - -export const handler = async (client: ImageKit, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts deleted file mode 100644 index ba7083d7..00000000 --- a/packages/mcp-server/src/tools/index.ts +++ /dev/null @@ -1,153 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, Endpoint, HandlerFunction } from './types'; - -export { Metadata, Endpoint, HandlerFunction }; - -import create_custom_metadata_fields from './custom-metadata-fields/create-custom-metadata-fields'; -import update_custom_metadata_fields from './custom-metadata-fields/update-custom-metadata-fields'; -import list_custom_metadata_fields from './custom-metadata-fields/list-custom-metadata-fields'; -import delete_custom_metadata_fields from './custom-metadata-fields/delete-custom-metadata-fields'; -import update_files from './files/update-files'; -import delete_files from './files/delete-files'; -import copy_files from './files/copy-files'; -import get_files from './files/get-files'; -import move_files from './files/move-files'; -import rename_files from './files/rename-files'; -import upload_files from './files/upload-files'; -import delete_files_bulk from './files/bulk/delete-files-bulk'; -import add_tags_files_bulk from './files/bulk/add-tags-files-bulk'; -import remove_ai_tags_files_bulk from './files/bulk/remove-ai-tags-files-bulk'; -import remove_tags_files_bulk from './files/bulk/remove-tags-files-bulk'; -import list_files_versions from './files/versions/list-files-versions'; -import delete_files_versions from './files/versions/delete-files-versions'; -import get_files_versions from './files/versions/get-files-versions'; -import restore_files_versions from './files/versions/restore-files-versions'; -import get_files_metadata from './files/metadata/get-files-metadata'; -import get_from_url_files_metadata from './files/metadata/get-from-url-files-metadata'; -import list_assets from './assets/list-assets'; -import create_cache_invalidation from './cache/invalidation/create-cache-invalidation'; -import get_cache_invalidation from './cache/invalidation/get-cache-invalidation'; -import create_folders from './folders/create-folders'; -import delete_folders from './folders/delete-folders'; -import copy_folders from './folders/copy-folders'; -import move_folders from './folders/move-folders'; -import rename_folders from './folders/rename-folders'; -import get_folders_job from './folders/job/get-folders-job'; -import get_accounts_usage from './accounts/usage/get-accounts-usage'; -import create_accounts_origins from './accounts/origins/create-accounts-origins'; -import update_accounts_origins from './accounts/origins/update-accounts-origins'; -import list_accounts_origins from './accounts/origins/list-accounts-origins'; -import delete_accounts_origins from './accounts/origins/delete-accounts-origins'; -import get_accounts_origins from './accounts/origins/get-accounts-origins'; -import create_accounts_url_endpoints from './accounts/url-endpoints/create-accounts-url-endpoints'; -import update_accounts_url_endpoints from './accounts/url-endpoints/update-accounts-url-endpoints'; -import list_accounts_url_endpoints from './accounts/url-endpoints/list-accounts-url-endpoints'; -import delete_accounts_url_endpoints from './accounts/url-endpoints/delete-accounts-url-endpoints'; -import get_accounts_url_endpoints from './accounts/url-endpoints/get-accounts-url-endpoints'; -import upload_v2_beta_files from './beta/v2/files/upload-v2-beta-files'; - -export const endpoints: Endpoint[] = []; - -function addEndpoint(endpoint: Endpoint) { - endpoints.push(endpoint); -} - -addEndpoint(create_custom_metadata_fields); -addEndpoint(update_custom_metadata_fields); -addEndpoint(list_custom_metadata_fields); -addEndpoint(delete_custom_metadata_fields); -addEndpoint(update_files); -addEndpoint(delete_files); -addEndpoint(copy_files); -addEndpoint(get_files); -addEndpoint(move_files); -addEndpoint(rename_files); -addEndpoint(upload_files); -addEndpoint(delete_files_bulk); -addEndpoint(add_tags_files_bulk); -addEndpoint(remove_ai_tags_files_bulk); -addEndpoint(remove_tags_files_bulk); -addEndpoint(list_files_versions); -addEndpoint(delete_files_versions); -addEndpoint(get_files_versions); -addEndpoint(restore_files_versions); -addEndpoint(get_files_metadata); -addEndpoint(get_from_url_files_metadata); -addEndpoint(list_assets); -addEndpoint(create_cache_invalidation); -addEndpoint(get_cache_invalidation); -addEndpoint(create_folders); -addEndpoint(delete_folders); -addEndpoint(copy_folders); -addEndpoint(move_folders); -addEndpoint(rename_folders); -addEndpoint(get_folders_job); -addEndpoint(get_accounts_usage); -addEndpoint(create_accounts_origins); -addEndpoint(update_accounts_origins); -addEndpoint(list_accounts_origins); -addEndpoint(delete_accounts_origins); -addEndpoint(get_accounts_origins); -addEndpoint(create_accounts_url_endpoints); -addEndpoint(update_accounts_url_endpoints); -addEndpoint(list_accounts_url_endpoints); -addEndpoint(delete_accounts_url_endpoints); -addEndpoint(get_accounts_url_endpoints); -addEndpoint(upload_v2_beta_files); - -export type Filter = { - type: 'resource' | 'operation' | 'tag' | 'tool'; - op: 'include' | 'exclude'; - value: string; -}; - -export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { - const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); - const unmatchedFilters = new Set(filters); - - const filtered = endpoints.filter((endpoint: Endpoint) => { - let included = false || allExcludes; - - for (const filter of filters) { - if (match(filter, endpoint)) { - unmatchedFilters.delete(filter); - included = filter.op === 'include'; - } - } - - return included; - }); - - // Check if any filters didn't match - const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); - if (unmatched.length > 0) { - throw new Error( - `The following filters did not match any endpoints: ${unmatched - .map((f) => `${f.type}=${f.value}`) - .join(', ')}`, - ); - } - - return filtered; -} - -function match({ type, value }: Filter, endpoint: Endpoint): boolean { - switch (type) { - case 'resource': { - const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; - const regex = new RegExp(regexStr); - return regex.test(normalizeResource(endpoint.metadata.resource)); - } - case 'operation': - return endpoint.metadata.operation === value; - case 'tag': - return endpoint.metadata.tags.includes(value); - case 'tool': - return endpoint.tool.name === value; - } -} - -function normalizeResource(resource: string): string { - return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); -} diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts deleted file mode 100644 index 8106d499..00000000 --- a/packages/mcp-server/src/tools/types.ts +++ /dev/null @@ -1,103 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import ImageKit from '@imagekit/nodejs'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; - -type TextContentBlock = { - type: 'text'; - text: string; -}; - -type ImageContentBlock = { - type: 'image'; - data: string; - mimeType: string; -}; - -type AudioContentBlock = { - type: 'audio'; - data: string; - mimeType: string; -}; - -type ResourceContentBlock = { - type: 'resource'; - resource: - | { - uri: string; - mimeType: string; - text: string; - } - | { - uri: string; - mimeType: string; - blob: string; - }; -}; - -export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock; - -export type ToolCallResult = { - content: ContentBlock[]; - isError?: boolean; -}; - -export type HandlerFunction = ( - client: ImageKit, - args: Record | undefined, -) => Promise; - -export function asTextContentResult(result: unknown): ToolCallResult { - return { - content: [ - { - type: 'text', - text: JSON.stringify(result, null, 2), - }, - ], - }; -} - -export async function asBinaryContentResult(response: Response): Promise { - const blob = await response.blob(); - const mimeType = blob.type; - const data = Buffer.from(await blob.arrayBuffer()).toString('base64'); - if (mimeType.startsWith('image/')) { - return { - content: [{ type: 'image', mimeType, data }], - }; - } else if (mimeType.startsWith('audio/')) { - return { - content: [{ type: 'audio', mimeType, data }], - }; - } else { - return { - content: [ - { - type: 'resource', - resource: { - // We must give a URI, even though this isn't actually an MCP resource. - uri: 'resource://tool-response', - mimeType, - blob: data, - }, - }, - ], - }; - } -} - -export type Metadata = { - resource: string; - operation: 'read' | 'write'; - tags: string[]; - httpMethod?: string; - httpPath?: string; - operationId?: string; -}; - -export type Endpoint = { - metadata: Metadata; - tool: Tool; - handler: HandlerFunction; -}; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts deleted file mode 100644 index d6272f6c..00000000 --- a/packages/mcp-server/tests/compat.test.ts +++ /dev/null @@ -1,1166 +0,0 @@ -import { - truncateToolNames, - removeTopLevelUnions, - removeAnyOf, - inlineRefs, - applyCompatibilityTransformations, - removeFormats, - findUsedDefs, -} from '../src/compat'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { JSONSchema } from '../src/compat'; -import { Endpoint } from '../src/tools'; - -describe('truncateToolNames', () => { - it('should return original names when maxLength is 0 or negative', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 0)).toEqual(new Map()); - expect(truncateToolNames(names, -1)).toEqual(new Map()); - }); - - it('should return original names when all names are shorter than maxLength', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 10)).toEqual(new Map()); - }); - - it('should truncate names longer than maxLength', () => { - const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; - expect(truncateToolNames(names, 10)).toEqual( - new Map([ - ['very-long-tool-name', 'very-long-'], - ['another-long-tool-name', 'another-lo'], - ]), - ); - }); - - it('should handle duplicate truncated names by appending numbers', () => { - const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; - expect(truncateToolNames(names, 8)).toEqual( - new Map([ - ['tool-name-a', 'tool-na1'], - ['tool-name-b', 'tool-na2'], - ['tool-name-c', 'tool-na3'], - ]), - ); - }); -}); - -describe('removeTopLevelUnions', () => { - const createTestTool = (overrides = {}): Tool => ({ - name: 'test-tool', - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - it('should return the original tool if it has no anyOf at the top level', () => { - const tool = createTestTool({ - inputSchema: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }, - }); - - expect(removeTopLevelUnions(tool)).toEqual([tool]); - }); - - it('should split a tool with top-level anyOf into multiple tools', () => { - const tool = createTestTool({ - name: 'union-tool', - description: 'A tool with unions', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - description: 'Its the first variant', - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'union-tool_first_variant', - description: 'Its the first variant', - inputSchema: { - type: 'object', - title: 'first variant', - description: 'Its the first variant', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - }, - { - name: 'union-tool_second_variant', - description: 'A tool with unions', - inputSchema: { - type: 'object', - title: 'second variant', - description: 'A tool with unions', - properties: { - common: { type: 'string' }, - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - }, - ]); - }); - - it('should handle $defs and only include those used by the variant', () => { - const tool = createTestTool({ - name: 'defs-tool', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - def2: { type: 'number', minimum: 0 }, - unused: { type: 'boolean' }, - }, - anyOf: [ - { - properties: { - email: { $ref: '#/$defs/def1' }, - }, - }, - { - properties: { - count: { $ref: '#/$defs/def2' }, - }, - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'defs-tool_variant1', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - email: { $ref: '#/$defs/def1' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - }, - }, - }, - { - name: 'defs-tool_variant2', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - count: { $ref: '#/$defs/def2' }, - }, - $defs: { - def2: { type: 'number', minimum: 0 }, - }, - }, - }, - ]); - }); -}); - -describe('removeAnyOf', () => { - it('should return original schema if it has no anyOf', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'number' }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(schema); - }); - - it('should remove anyOf field and use the first variant', () => { - const schema = { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }; - - const expected = { - type: 'object', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should recursively remove anyOf fields from nested properties', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - }, - anyOf: [ - { - properties: { - option1: { type: 'boolean' }, - }, - }, - { - properties: { - option2: { type: 'array' }, - }, - }, - ], - }, - }, - }; - - const expected = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - option1: { type: 'boolean' }, - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should handle arrays', () => { - const schema = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); -}); - -describe('findUsedDefs', () => { - it('should handle circular references without stack overflow', () => { - const defs = { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/person' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('person'); - }).not.toThrow(); - }); - - it('should handle indirect circular references without stack overflow', () => { - const defs = { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Indirect circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - root: { $ref: '#/$defs/node' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('node'); - expect(result).toHaveProperty('childNode'); - }).not.toThrow(); - }); - - it('should find all used definitions in non-circular schemas', () => { - const defs = { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - address: { $ref: '#/$defs/address' }, - }, - }, - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - }, - unused: { - type: 'object', - properties: { - data: { type: 'string' }, - }, - }, - }; - - const schema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/user' }, - }, - }; - - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('user'); - expect(result).toHaveProperty('address'); - expect(result).not.toHaveProperty('unused'); - }); -}); - -describe('inlineRefs', () => { - it('should return the original schema if it does not contain $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - }, - }; - - expect(inlineRefs(schema)).toEqual(schema); - }); - - it('should inline simple $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should inline nested $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - order: { $ref: '#/$defs/order' }, - }, - $defs: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { type: 'array', items: { $ref: '#/$defs/item' } }, - }, - }, - item: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { - type: 'array', - items: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle circular references by removing the circular part', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/person' }, - }, - $defs: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - // friend property is removed to break the circular reference - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle indirect circular references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - node: { $ref: '#/$defs/node' }, - }, - $defs: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Circular reference through childNode - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { - type: 'object', - properties: { - value: { type: 'string' }, - // parent property is removed to break the circular reference - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should preserve other properties when inlining references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - address: { $ref: '#/$defs/address', description: 'User address' }, - }, - $defs: { - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - address: { - type: 'object', - description: 'User address', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); -}); - -describe('removeFormats', () => { - it('should return original schema if formats capability is true', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - expect(removeFormats(schema, true)).toEqual(schema); - }); - - it('should move format to description when formats capability is false', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - email: { type: 'string', description: 'An email field (format: "email")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle properties without description', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', format: 'date' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: '(format: "date")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle nested properties', () => { - const schema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle arrays of objects', () => { - const schema = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date', format: 'date' }, - end: { type: 'string', description: 'End date', format: 'date' }, - }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date (format: "date")' }, - end: { type: 'string', description: 'End date (format: "date")' }, - }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle schemas with $defs', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field', - format: 'date-time', - }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field (format: "date-time")', - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); -}); - -describe('applyCompatibilityTransformations', () => { - const createTestTool = (name: string, overrides = {}): Tool => ({ - name, - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - const createTestEndpoint = (tool: Tool): Endpoint => ({ - tool, - handler: jest.fn(), - metadata: { - resource: 'test', - operation: 'read' as const, - tags: [], - }, - }); - - it('should not modify endpoints when all capabilities are enabled', () => { - const tool = createTestTool('test-tool'); - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed).toEqual(endpoints); - }); - - it('should split tools with top-level unions when topLevelUnions is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); - expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); - }); - - it('should handle variants without titles in removeTopLevelUnions', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - }, - { - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); - expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); - }); - - it('should truncate tool names when toolNameLength is set', () => { - const tools = [ - createTestTool('very-long-tool-name-that-exceeds-limit'), - createTestTool('another-long-tool-name-to-truncate'), - createTestTool('short-name'), - ]; - - const endpoints = tools.map(createTestEndpoint); - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); - expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); - expect(transformed[2]!.tool.name).toBe('short-name'); - }); - - it('should inline refs when refs capability is disabled', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - expect(schema.$defs).toBeUndefined(); - - if (schema.properties) { - expect(schema.properties['user']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }); - } - }); - - it('should preserve external refs when inlining', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - internal: { $ref: '#/$defs/internal' }, - external: { $ref: 'https://example.com/schemas/external.json' }, - }, - $defs: { - internal: { - type: 'object', - properties: { - name: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties) { - expect(schema.properties['internal']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - }, - }); - expect(schema.properties['external']).toEqual({ - $ref: 'https://example.com/schemas/external.json', - }); - } - }); - - it('should remove anyOf fields when unions capability is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - field: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['field']) { - const field = schema.properties['field']; - expect(field.anyOf).toBeUndefined(); - expect(field.type).toBe('string'); - } - }); - - it('should correctly combine topLevelUnions and toolNameLength transformations', () => { - const tool = createTestTool('very-long-union-tool-name', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - - // Both names should be truncated because they exceed 20 characters - expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); - expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); - }); - - it('should correctly combine refs and unions transformations', () => { - const tool = createTestTool('complex-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - preference: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - // Refs should be inlined - expect(schema.$defs).toBeUndefined(); - - // Safely access nested properties - if (schema.properties && schema.properties['user']) { - const user = schema.properties['user']; - // User should be inlined - expect(user.type).toBe('object'); - - // AnyOf in the inlined user.preference should be removed - if (user.properties && user.properties['preference']) { - const preference = user.properties['preference']; - expect(preference.anyOf).toBeUndefined(); - expect(preference.type).toBe('string'); - } - } - }); - - it('should handle formats capability being false', () => { - const tool = createTestTool('format-tool', { - inputSchema: { - type: 'object', - properties: { - date: { type: 'string', description: 'A date', format: 'date' }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: false, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['date']) { - const dateField = schema.properties['date']; - expect(dateField['format']).toBeUndefined(); - expect(dateField['description']).toBe('A date (format: "date")'); - } - }); -}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts deleted file mode 100644 index 08963af8..00000000 --- a/packages/mcp-server/tests/dynamic-tools.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { dynamicTools } from '../src/dynamic-tools'; -import { Endpoint } from '../src/tools'; - -describe('dynamicTools', () => { - const fakeClient = {} as any; - - const endpoints: Endpoint[] = [ - makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), - makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), - makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), - makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), - ]; - - const tools = dynamicTools(endpoints); - - const toolsMap = { - list_api_endpoints: toolOrError('list_api_endpoints'), - get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), - invoke_api_endpoint: toolOrError('invoke_api_endpoint'), - }; - - describe('list_api_endpoints', () => { - it('should return all endpoints when no search query is provided', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(endpoints.length); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); - }); - - it('should filter endpoints by name', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - - it('should filter endpoints by resource', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); - }); - - it('should filter endpoints by tag', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); - }); - - it('should be case insensitive in search', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.length).toBe(2); - result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { - expect( - tool.name.toLowerCase().includes('admin') || - tool.resource.toLowerCase().includes('admin') || - tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), - ).toBeTruthy(); - }); - }); - - it('should filter endpoints by description', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'Test endpoint for user_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); - }); - - it('should filter endpoints by partial description match', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'endpoint for user', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - }); - - describe('get_api_endpoint_schema', () => { - it('should return schema for existing endpoint', async () => { - const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { - endpoint: 'test_read_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result).toEqual(endpoints[0]?.tool); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), - ).rejects.toThrow('Endpoint non_existent_endpoint not found'); - }); - - it('should throw error when no endpoint provided', async () => { - await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - }); - - describe('invoke_api_endpoint', () => { - it('should successfully invoke endpoint with valid arguments', async () => { - const mockHandler = endpoints[0]?.handler as jest.Mock; - mockHandler.mockClear(); - - await toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { testParam: 'test value' }, - }); - - expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'non_existent_endpoint', - args: { testParam: 'test value' }, - }), - ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); - }); - - it('should throw error when no arguments provided', async () => { - await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - - it('should throw error for invalid argument schema', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { wrongParam: 'test value' }, // Missing required testParam - }), - ).rejects.toThrow(/Invalid arguments for endpoint/); - }); - }); - - function toolOrError(name: string) { - const tool = tools.find((tool) => tool.tool.name === name); - if (!tool) throw new Error(`Tool ${name} not found`); - return tool; - } -}); - -function makeEndpoint( - name: string, - resource: string, - operation: 'read' | 'write', - tags: string[] = [], -): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { - name, - description: `Test endpoint for ${name}`, - inputSchema: { - type: 'object', - properties: { - testParam: { type: 'string' }, - }, - required: ['testParam'], - }, - }, - handler: jest.fn().mockResolvedValue({ success: true }), - }; -} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts deleted file mode 100644 index a8a5b81a..00000000 --- a/packages/mcp-server/tests/options.test.ts +++ /dev/null @@ -1,518 +0,0 @@ -import { parseCLIOptions, parseQueryOptions } from '../src/options'; -import { Filter } from '../src/tools'; -import { parseEmbeddedJSON } from '../src/compat'; - -// Mock process.argv -const mockArgv = (args: string[]) => { - const originalArgv = process.argv; - process.argv = ['node', 'test.js', ...args]; - return () => { - process.argv = originalArgv; - }; -}; - -describe('parseCLIOptions', () => { - it('should parse basic filter options', () => { - const cleanup = mockArgv([ - '--tool=test-tool', - '--resource=test-resource', - '--operation=read', - '--tag=test-tag', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - { type: 'operation', op: 'include', value: 'read' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - expect(result.list).toBe(false); - - cleanup(); - }); - - it('should parse exclusion filters', () => { - const cleanup = mockArgv([ - '--no-tool=exclude-tool', - '--no-resource=exclude-resource', - '--no-operation=write', - '--no-tag=exclude-tag', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - cleanup(); - }); - - it('should parse client presets', () => { - const cleanup = mockArgv(['--client=openai-agents']); - - const result = parseCLIOptions(); - - expect(result.client).toEqual('openai-agents'); - - cleanup(); - }); - - it('should parse individual capabilities', () => { - const cleanup = mockArgv([ - '--capability=top-level-unions', - '--capability=valid-json', - '--capability=refs', - '--capability=unions', - '--capability=tool-name-length=40', - ]); - - const result = parseCLIOptions(); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - toolNameLength: 40, - }); - - cleanup(); - }); - - it('should handle list option', () => { - const cleanup = mockArgv(['--list']); - - const result = parseCLIOptions(); - - expect(result.list).toBe(true); - - cleanup(); - }); - - it('should handle multiple filters of the same type', () => { - const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - cleanup(); - }); - - it('should handle comma-separated values in array options', () => { - const cleanup = mockArgv([ - '--tool=tool1,tool2', - '--resource=res1,res2', - '--capability=top-level-unions,valid-json,unions', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - unions: true, - }); - - cleanup(); - }); - - it('should handle invalid tool-name-length format', () => { - const cleanup = mockArgv(['--capability=tool-name-length=invalid']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; - cleanup(); - }); - - it('should handle unknown capability', () => { - const cleanup = mockArgv(['--capability=unknown-capability']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; - cleanup(); - }); -}); - -describe('parseQueryOptions', () => { - const defaultOptions = { - client: undefined, - includeDynamicTools: undefined, - includeAllTools: undefined, - filters: [], - capabilities: { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - }; - - it('should parse basic filter options from query string', () => { - const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - ]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }); - }); - - it('should parse exclusion filters from query string', () => { - const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'operation', op: 'exclude', value: 'write' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('should parse client option from query string', () => { - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should parse client capabilities from query string', () => { - const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 40, - }); - }); - - it('should parse no-capability options from query string', () => { - const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: false, - validJson: true, - refs: false, - unions: true, - formats: false, - toolNameLength: undefined, - }); - }); - - it('should parse tools options from query string', () => { - const query = 'tools=dynamic&tools=all'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(true); - expect(result.includeAllTools).toBe(true); - }); - - it('should parse no-tools options from query string', () => { - const query = 'tools=dynamic&tools=all&no_tools=dynamic'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(false); - expect(result.includeAllTools).toBe(true); - }); - - it('should handle array values in query string', () => { - const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ]); - }); - - it('should merge with default options', () => { - const defaultWithFilters = { - ...defaultOptions, - filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], - client: 'cursor' as const, - includeDynamicTools: true, - }; - - const query = 'tool=new-tool&resource=new-resource'; - const result = parseQueryOptions(defaultWithFilters, query); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'existing-tag' }, - { type: 'resource', op: 'include', value: 'new-resource' }, - { type: 'tool', op: 'include', value: 'new-tool' }, - ]); - - expect(result.client).toBe('cursor'); - expect(result.includeDynamicTools).toBe(true); - }); - - it('should override client from default options', () => { - const defaultWithClient = { - ...defaultOptions, - client: 'cursor' as const, - }; - - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultWithClient, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should merge capabilities with default options', () => { - const defaultWithCapabilities = { - ...defaultOptions, - capabilities: { - topLevelUnions: false, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: 30, - }, - }; - - const query = 'capability=top-level-unions&no_capability=refs'; - const result = parseQueryOptions(defaultWithCapabilities, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: false, - refs: false, - unions: true, - formats: true, - toolNameLength: 30, - }); - }); - - it('should handle empty query string', () => { - const query = ''; - const result = parseQueryOptions(defaultOptions, query); - - expect(result).toEqual(defaultOptions); - }); - - it('should handle invalid query string gracefully', () => { - const query = 'invalid=value&operation=invalid-operation'; - - // Should throw due to Zod validation for invalid operation - expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); - }); - - it('should preserve default undefined values when not specified', () => { - const defaultWithUndefined = { - ...defaultOptions, - client: undefined, - includeDynamicTools: undefined, - includeAllTools: undefined, - }; - - const query = 'tool=test-tool'; - const result = parseQueryOptions(defaultWithUndefined, query); - - expect(result.client).toBeUndefined(); - expect(result.includeDynamicTools).toBeFalsy(); - expect(result.includeAllTools).toBeFalsy(); - }); - - it('should handle complex query with mixed include and exclude filters', () => { - const query = - 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'include-res' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'include-tag' }, - { type: 'tool', op: 'include', value: 'include-tool' }, - { type: 'resource', op: 'exclude', value: 'exclude-res' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); -}); - -describe('parseEmbeddedJSON', () => { - it('should not change non-string values', () => { - const args = { - numberProp: 42, - booleanProp: true, - objectProp: { nested: 'value' }, - arrayProp: [1, 2, 3], - nullProp: null, - undefinedProp: undefined, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberProp']).toBe(42); - expect(result['booleanProp']).toBe(true); - expect(result['objectProp']).toEqual({ nested: 'value' }); - expect(result['arrayProp']).toEqual([1, 2, 3]); - expect(result['nullProp']).toBe(null); - expect(result['undefinedProp']).toBe(undefined); - }); - - it('should parse valid JSON objects in string properties', () => { - const args = { - jsonObjectString: '{"key": "value", "number": 123}', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since changes were made - expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); - expect(result['regularString']).toBe('not json'); - }); - - it('should leave invalid JSON in string properties unchanged', () => { - const args = { - invalidJson1: '{"key": value}', // Missing quotes around value - invalidJson2: '{key: "value"}', // Missing quotes around key - invalidJson3: '{"key": "value",}', // Trailing comma - invalidJson4: 'just a regular string', - emptyString: '', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['invalidJson1']).toBe('{"key": value}'); - expect(result['invalidJson2']).toBe('{key: "value"}'); - expect(result['invalidJson3']).toBe('{"key": "value",}'); - expect(result['invalidJson4']).toBe('just a regular string'); - expect(result['emptyString']).toBe(''); - }); - - it('should not parse JSON primitives in string properties', () => { - const args = { - numberString: '123', - floatString: '45.67', - negativeNumberString: '-89', - booleanTrueString: 'true', - booleanFalseString: 'false', - nullString: 'null', - jsonArrayString: '[1, 2, 3, "test"]', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberString']).toBe('123'); - expect(result['floatString']).toBe('45.67'); - expect(result['negativeNumberString']).toBe('-89'); - expect(result['booleanTrueString']).toBe('true'); - expect(result['booleanFalseString']).toBe('false'); - expect(result['nullString']).toBe('null'); - expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); - expect(result['regularString']).toBe('not json'); - }); - - it('should handle mixed valid objects and other JSON types', () => { - const args = { - validObject: '{"success": true}', - invalidObject: '{"missing": quote}', - validNumber: '42', - validArray: '[1, 2, 3]', - keepAsString: 'hello world', - nonString: 123, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since some changes were made - expect(result['validObject']).toEqual({ success: true }); - expect(result['invalidObject']).toBe('{"missing": quote}'); - expect(result['validNumber']).toBe('42'); // Not parsed, remains string - expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string - expect(result['keepAsString']).toBe('hello world'); - expect(result['nonString']).toBe(123); - }); - - it('should return original object when no strings are present', () => { - const args = { - number: 42, - boolean: true, - object: { key: 'value' }, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); - - it('should return original object when all strings are invalid JSON', () => { - const args = { - string1: 'hello', - string2: 'world', - string3: 'not json at all', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); -}); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts deleted file mode 100644 index cfff24a2..00000000 --- a/packages/mcp-server/tests/tools.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { Endpoint, Filter, Metadata, query } from '../src/tools'; - -describe('Endpoint filtering', () => { - const endpoints: Endpoint[] = [ - endpoint({ - resource: 'user', - operation: 'read', - tags: ['admin'], - toolName: 'retrieve_user', - }), - endpoint({ - resource: 'user.profile', - operation: 'write', - tags: [], - toolName: 'create_user_profile', - }), - endpoint({ - resource: 'user.profile', - operation: 'read', - tags: [], - toolName: 'get_user_profile', - }), - endpoint({ - resource: 'user.roles.permissions', - operation: 'write', - tags: ['admin', 'security'], - toolName: 'update_user_role_permissions', - }), - endpoint({ - resource: 'documents.metadata.tags', - operation: 'write', - tags: ['taxonomy', 'metadata'], - toolName: 'create_document_metadata_tags', - }), - endpoint({ - resource: 'organization.settings', - operation: 'read', - tags: ['admin', 'configuration'], - toolName: 'get_organization_settings', - }), - ]; - - const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ - { - name: 'match none', - filters: [], - expected: [], - }, - - // Resource tests - { - name: 'simple resource', - filters: [{ type: 'resource', op: 'include', value: 'user' }], - expected: ['retrieve_user'], - }, - { - name: 'exclude resource', - filters: [{ type: 'resource', op: 'exclude', value: 'user' }], - expected: [ - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'create_document_metadata_tags', - 'get_organization_settings', - ], - }, - { - name: 'resource and subresources', - filters: [{ type: 'resource', op: 'include', value: 'user*' }], - expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'just subresources', - filters: [{ type: 'resource', op: 'include', value: 'user.*' }], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'specific subresource', - filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], - expected: ['update_user_role_permissions'], - }, - { - name: 'deep wildcard match', - filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], - expected: ['create_document_metadata_tags'], - }, - - // Operation tests - { - name: 'read operation', - filters: [{ type: 'operation', op: 'include', value: 'read' }], - expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], - }, - { - name: 'write operation', - filters: [{ type: 'operation', op: 'include', value: 'write' }], - expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], - }, - { - name: 'resource and operation combined', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ], - expected: ['get_user_profile'], - }, - - // Tag tests - { - name: 'admin tag', - filters: [{ type: 'tag', op: 'include', value: 'admin' }], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'taxonomy tag', - filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], - expected: ['create_document_metadata_tags'], - }, - { - name: 'multiple tags (OR logic)', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'include', value: 'security' }, - ], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'excluding a tag', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'exclude', value: 'security' }, - ], - expected: ['retrieve_user', 'get_organization_settings'], - }, - - // Tool name tests - { - name: 'tool name match', - filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], - expected: ['get_organization_settings'], - }, - { - name: 'two tools match', - filters: [ - { type: 'tool', op: 'include', value: 'get_organization_settings' }, - { type: 'tool', op: 'include', value: 'create_user_profile' }, - ], - expected: ['create_user_profile', 'get_organization_settings'], - }, - { - name: 'excluding tool by name', - filters: [ - { type: 'resource', op: 'include', value: 'user*' }, - { type: 'tool', op: 'exclude', value: 'retrieve_user' }, - ], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - - // Complex combinations - { - name: 'complex filter: read operations with admin tag', - filters: [ - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - { - name: 'complex filter: user resources with no tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'exclude', value: 'admin' }, - ], - expected: ['create_user_profile', 'get_user_profile'], - }, - { - name: 'complex filter: user resources and tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - ]; - - tests.forEach((test) => { - it(`filters by ${test.name}`, () => { - const filtered = query(test.filters, endpoints); - expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); - }); - }); -}); - -function endpoint({ - resource, - operation, - tags, - toolName, -}: { - resource: string; - operation: Metadata['operation']; - tags: string[]; - toolName: string; -}): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, - handler: jest.fn(), - }; -} diff --git a/packages/mcp-server/tsc-multi.json b/packages/mcp-server/tsc-multi.json deleted file mode 100644 index 4facad5a..00000000 --- a/packages/mcp-server/tsc-multi.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "targets": [ - { "extname": ".js", "module": "commonjs" }, - { "extname": ".mjs", "module": "esnext" } - ], - "projects": ["tsconfig.build.json"] -} diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json deleted file mode 100644 index 047e086f..00000000 --- a/packages/mcp-server/tsconfig.build.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist/src"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist/src", - "paths": { - "@imagekit/nodejs-mcp/*": ["dist/src/*"], - "@imagekit/nodejs-mcp": ["dist/src/index.ts"] - }, - "noEmit": false, - "declaration": true, - "declarationMap": true, - "outDir": "dist", - "pretty": true, - "sourceMap": true - } -} diff --git a/packages/mcp-server/tsconfig.dist-src.json b/packages/mcp-server/tsconfig.dist-src.json deleted file mode 100644 index e9f2d70b..00000000 --- a/packages/mcp-server/tsconfig.dist-src.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - // this config is included in the published src directory to prevent TS errors - // from appearing when users go to source, and VSCode opens the source .ts file - // via declaration maps - "include": ["index.ts"], - "compilerOptions": { - "target": "es2015", - "lib": ["DOM"], - "moduleResolution": "node" - } -} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json deleted file mode 100644 index ddbe0074..00000000 --- a/packages/mcp-server/tsconfig.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "include": ["src", "tests", "examples"], - "exclude": [], - "compilerOptions": { - "target": "es2020", - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "baseUrl": "./", - "paths": { - "@imagekit/nodejs-mcp/*": ["src/*"], - "@imagekit/nodejs-mcp": ["src/index.ts"] - }, - "noEmit": true, - - "resolveJsonModule": true, - - "forceConsistentCasingInFileNames": true, - - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "alwaysStrict": true, - "exactOptionalPropertyTypes": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - - "skipLibCheck": true - } -} diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock deleted file mode 100644 index 707a2de8..00000000 --- a/packages/mcp-server/yarn.lock +++ /dev/null @@ -1,3606 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" - integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" - integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helpers" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" - integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== - dependencies: - "@babel/parser" "^7.27.1" - "@babel/types" "^7.27.1" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.27.1": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" - integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" - integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== - dependencies: - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" - integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/template@^7.27.1", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" - integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.27.1" - "@babel/parser" "^7.27.1" - "@babel/template" "^7.27.1" - "@babel/types" "^7.27.1" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" - integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cloudflare/cabidela@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@cloudflare/cabidela/-/cabidela-0.2.4.tgz#9a3e9212e636a24d796a8f16741c24885b326a1a" - integrity sha512-u/1OwwqfcMvjmUFOcb6QtFzVVGpncHJxwl254wjzp0JC5CUlBkV6r5BbRrHI5ZYJEAgu8NeeorirxngmMFPZjQ== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" - integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== - -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== - dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@modelcontextprotocol/sdk@^1.11.5": - version "1.17.3" - resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" - integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== - dependencies: - ajv "^6.12.6" - content-type "^1.0.5" - cors "^2.8.5" - cross-spawn "^7.0.5" - eventsource "^3.0.2" - eventsource-parser "^3.0.0" - express "^5.0.1" - express-rate-limit "^7.5.0" - pkce-challenge "^5.0.0" - raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/core@^0.2.3": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" - integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@ts-morph/common@~0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" - integrity sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q== - dependencies: - fast-glob "^3.2.12" - minimatch "^7.4.3" - mkdirp "^2.1.6" - path-browserify "^1.0.1" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" - integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== - dependencies: - "@babel/types" "^7.20.7" - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@^5.0.0": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" - integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" - integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.4.0": - version "29.5.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node@*": - version "22.15.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" - integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== - dependencies: - undici-types "~6.21.0" - -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" - integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/type-utils" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/parser@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" - integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== - dependencies: - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" - integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - -"@typescript-eslint/type-utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" - integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== - dependencies: - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - debug "^4.3.4" - ts-api-utils "^2.0.1" - -"@typescript-eslint/types@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" - integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== - -"@typescript-eslint/typescript-estree@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" - integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" - integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - -"@typescript-eslint/visitor-keys@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" - integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== - dependencies: - "@typescript-eslint/types" "8.31.1" - eslint-visitor-keys "^4.2.0" - -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -accepts@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" - integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== - dependencies: - mime-types "^3.0.0" - negotiator "^1.0.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.12.4, ajv@^6.12.6: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" - integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -body-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" - integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== - dependencies: - bytes "^3.1.2" - content-type "^1.0.5" - debug "^4.4.0" - http-errors "^2.0.0" - iconv-lite "^0.6.3" - on-finished "^2.4.1" - qs "^6.14.0" - raw-body "^3.0.0" - type-is "^2.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0: - version "4.24.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" - integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== - dependencies: - caniuse-lite "^1.0.30001716" - electron-to-chromium "^1.5.149" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" - -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.1.2, bytes@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001716: - version "1.0.30001717" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" - integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== - -chalk@^4.0.0, chalk@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -code-block-writer@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" - integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" - integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== - dependencies: - safe-buffer "5.2.1" - -content-type@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" - integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== - -cookie@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - -dedent@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" - integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -depd@2.0.0, depd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -electron-to-chromium@^1.5.149: - version "1.5.151" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz#5edd6c17e1b2f14b4662c41b9379f96cc8c2bb7c" - integrity sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-prettier@^5.0.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b" - integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.11.0" - -eslint-plugin-unused-imports@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d" - integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ== - dependencies: - eslint-rule-composer "^0.3.0" - -eslint-rule-composer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" - integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - -eslint@^8.49.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventsource-parser@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8" - integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA== - -eventsource-parser@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" - integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== - -eventsource@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" - integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== - dependencies: - eventsource-parser "^3.0.1" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - -express-rate-limit@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" - integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== - -express@^5.0.1, express@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" - integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== - dependencies: - accepts "^2.0.0" - body-parser "^2.2.0" - content-disposition "^1.0.0" - content-type "^1.0.5" - cookie "^0.7.1" - cookie-signature "^1.2.1" - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - finalhandler "^2.1.0" - fresh "^2.0.0" - http-errors "^2.0.0" - merge-descriptors "^2.0.0" - mime-types "^3.0.0" - on-finished "^2.4.1" - once "^1.4.0" - parseurl "^1.3.3" - proxy-addr "^2.0.7" - qs "^6.14.0" - range-parser "^1.2.1" - router "^2.2.0" - send "^1.1.0" - serve-static "^2.2.0" - statuses "^2.0.1" - type-is "^2.0.1" - vary "^1.1.2" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@^3.2.12, fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" - integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== - dependencies: - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - on-finished "^2.4.1" - parseurl "^1.3.3" - statuses "^2.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" - integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stdin@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.6.3, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ignore@^5.2.0, ignore@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-promise@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" - integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.4.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": - version "0.8.6" - resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@^1.1.1, make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" - integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== - -merge-descriptors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" - integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@^1.54.0: - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^3.0.0, mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== - dependencies: - mime-db "^1.54.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^7.4.3: - version "7.4.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" - integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" - integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" - integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -p-all@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" - integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== - dependencies: - p-map "^4.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-browserify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" - integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkce-challenge@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" - integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3.0.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" - integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== - -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -qs@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== - dependencies: - side-channel "^1.1.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" - integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.6.3" - unpipe "1.0.0" - -react-is@^18.0.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - -resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -router@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" - integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== - dependencies: - debug "^4.4.0" - depd "^2.0.0" - is-promise "^4.0.0" - parseurl "^1.3.3" - path-to-regexp "^8.0.0" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: - version "7.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" - integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== - -send@^1.1.0, send@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" - integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== - dependencies: - debug "^4.3.5" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - fresh "^2.0.0" - http-errors "^2.0.0" - mime-types "^3.0.1" - ms "^2.1.3" - on-finished "^2.4.1" - range-parser "^1.2.1" - statuses "^2.0.1" - -serve-static@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" - integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== - dependencies: - encodeurl "^2.0.0" - escape-html "^1.0.3" - parseurl "^1.3.3" - send "^1.2.0" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -statuses@2.0.1, statuses@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-to-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" - integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== - dependencies: - readable-stream "^3.4.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -superstruct@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" - integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -synckit@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" - integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== - dependencies: - "@pkgr/core" "^0.2.3" - tslib "^2.8.1" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -ts-api-utils@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== - -ts-jest@^29.1.0: - version "29.3.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.2.tgz#0576cdf0a507f811fe73dcd16d135ce89f8156cb" - integrity sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug== - dependencies: - bs-logger "^0.2.6" - ejs "^3.1.10" - fast-json-stable-stringify "^2.1.0" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.7.1" - type-fest "^4.39.1" - yargs-parser "^21.1.1" - -ts-morph@^19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-19.0.0.tgz#43e95fb0156c3fe3c77c814ac26b7d0be2f93169" - integrity sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ== - dependencies: - "@ts-morph/common" "~0.20.0" - code-block-writer "^12.0.0" - -ts-node@^10.5.0: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz": - version "1.1.8" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04" - dependencies: - debug "^4.3.7" - fast-glob "^3.3.2" - get-stdin "^8.0.0" - p-all "^3.0.0" - picocolors "^1.1.1" - signal-exit "^3.0.7" - string-to-stream "^3.0.1" - superstruct "^1.0.4" - tslib "^2.8.1" - yargs "^17.7.2" - -tsconfig-paths@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^4.39.1: - version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" - integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== - -type-is@^2.0.0, type-is@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" - integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== - dependencies: - content-type "^1.0.5" - media-typer "^1.1.0" - mime-types "^3.0.0" - -typescript@5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -vary@^1, vary@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1, yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - 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" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: - version "3.24.5" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" - integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== - -zod-validation-error@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" - integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== - -zod@^3.23.8: - version "3.24.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" - integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== - -zod@^3.25.20: - version "3.25.76" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" - integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/release-please-config.json b/release-please-config.json index b1909804..1ebd0bde 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -60,14 +60,5 @@ } ], "release-type": "node", - "extra-files": [ - "src/version.ts", - "README.md", - "packages/mcp-server/yarn.lock", - { - "type": "json", - "path": "packages/mcp-server/package.json", - "jsonpath": "$.version" - } - ] + "extra-files": ["src/version.ts", "README.md"] } diff --git a/scripts/build b/scripts/build index fe159668..470e580f 100755 --- a/scripts/build +++ b/scripts/build @@ -49,9 +49,3 @@ if [ -e ./scripts/build-deno ] then ./scripts/build-deno fi -# build all sub-packages -for dir in packages/*; do - if [ -d "$dir" ]; then - (cd "$dir" && yarn install && yarn build) - fi -done diff --git a/scripts/build-all b/scripts/build-all deleted file mode 100755 index 4e5ac01f..00000000 --- a/scripts/build-all +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -# build-all is deprecated, use build instead - -bash ./scripts/build diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts deleted file mode 100644 index 50e93fef..00000000 --- a/scripts/publish-packages.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Called from the `create-releases.yml` workflow with the output - * of the release please action as the first argument. - * - * Example JSON input: - * - * ```json - { - "releases_created": "true", - "release_created": "true", - "id": "137967744", - "name": "sdk: v0.14.5", - "tag_name": "sdk-v0.14.5", - "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", - "body": "## 0.14.5 (2024-01-22)\n\n...", - "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5", - "draft": "false", - "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}", - "path": ".", - "version": "0.14.5", - "major": "0", - "minor": "14", - "patch": "5", - "packages/additional-sdk--release_created": "true", - "packages/additional-sdk--id": "137967756", - "packages/additional-sdk--name": "additional-sdk: v0.5.2", - "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2", - "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", - "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...", - "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2", - "packages/additional-sdk--draft": "false", - "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}", - "packages/additional-sdk--path": "packages/additional-sdk", - "packages/additional-sdk--version": "0.5.2", - "packages/additional-sdk--major": "0", - "packages/additional-sdk--minor": "5", - "packages/additional-sdk--patch": "2", - "paths_released": "[\".\",\"packages/additional-sdk\"]" - } - ``` - */ - -import { execSync } from 'child_process'; -import path from 'path'; - -function main() { - const data = process.argv[2] ?? process.env['DATA']; - if (!data) { - throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`); - } - - const rootDir = path.join(__dirname, '..'); - console.log('root dir', rootDir); - console.log(`publish-packages called with ${data}`); - - const outputs = JSON.parse(data); - - const rawPaths = outputs.paths_released; - - if (!rawPaths) { - console.error(JSON.stringify(outputs, null, 2)); - throw new Error('Expected outputs to contain a truthy `paths_released` property'); - } - if (typeof rawPaths !== 'string') { - console.error(JSON.stringify(outputs, null, 2)); - throw new Error('Expected outputs `paths_released` property to be a JSON string'); - } - - const paths = JSON.parse(rawPaths); - if (!Array.isArray(paths)) { - console.error(JSON.stringify(outputs, null, 2)); - throw new Error('Expected outputs `paths_released` property to be an array'); - } - if (!paths.length) { - console.error(JSON.stringify(outputs, null, 2)); - throw new Error('Expected outputs `paths_released` property to contain at least one entry'); - } - - const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm'); - console.log('Using publish script at', publishScriptPath); - - console.log('Ensuring root package is built'); - console.log(`$ yarn build`); - execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' }); - - for (const relPackagePath of paths) { - console.log('\n'); - - const packagePath = path.join(rootDir, relPackagePath); - console.log(`Publishing in directory: ${packagePath}`); - - console.log(`$ yarn install`); - execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); - - console.log(`$ bash ${publishScriptPath}`); - execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); - } - - console.log('Finished publishing packages'); -} - -main(); diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs index 4d6634ea..7c24f56e 100644 --- a/scripts/utils/make-dist-package-json.cjs +++ b/scripts/utils/make-dist-package-json.cjs @@ -12,14 +12,6 @@ processExportMap(pkgJson.exports); for (const key of ['types', 'main', 'module']) { if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); } -// Fix bin paths if present -if (pkgJson.bin) { - for (const key in pkgJson.bin) { - if (typeof pkgJson.bin[key] === 'string') { - pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './'); - } - } -} delete pkgJson.devDependencies; delete pkgJson.scripts.prepack; From 898698108afffb5ecffda06765b7c02c21f2e74c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 13:21:44 +0000 Subject: [PATCH 05/73] feat(api): manual updates --- .stats.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9af54678..fa65ec98 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 -config_hash: 1335a5f946838eb26cf469ddf59cd223 +config_hash: 249ee22f294858ab0971b8379f7cb519 diff --git a/README.md b/README.md index cbefce31..4bd166cd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This library provides convenient access to the Image Kit REST API from server-side TypeScript or JavaScript. -The REST API documentation can be found on [imagekit.io](https://imagekit.io). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs). The full API of this library can be found in [api.md](api.md). It is generated with [Stainless](https://www.stainless.com/). From 4d7286a5b61168b8bccd44e2cf754938e63c8568 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 08:43:42 +0000 Subject: [PATCH 06/73] feat(api): manual updates --- .stats.yml | 4 +- api.md | 8 + src/client.ts | 8 + src/resources/index.ts | 4 + src/resources/shared.ts | 308 +++++++--- src/resources/webhooks.ts | 1227 ++++++++++++++++++++++++++++++++++++- 6 files changed, 1455 insertions(+), 104 deletions(-) diff --git a/.stats.yml b/.stats.yml index fa65ec98..caed6f5a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-81851750ac0e826623eae52a2361a85e97d1dc39cfcd47adea4b392440c897f1.yml -openapi_spec_hash: b36ee8d65b9b270168076c8d36420dc1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0cdbdd05084a8219bc003a34670472f198cf91e5f6402cede2cb1094b7bfd98d.yml +openapi_spec_hash: d337c89146f41fa215560bb5aef2e4ec config_hash: 249ee22f294858ab0971b8379f7cb519 diff --git a/api.md b/api.md index 15beb064..043e01a6 100644 --- a/api.md +++ b/api.md @@ -211,6 +211,14 @@ Types: - VideoTransformationAcceptedEvent - VideoTransformationErrorEvent - VideoTransformationReadyEvent +- UploadPreTransformSuccessWebhookEvent +- UploadPreTransformErrorWebhookEvent +- UploadPostTransformSuccessWebhookEvent +- UploadPostTransformErrorWebhookEvent +- UploadPreTransformSuccessWebhookEvent +- UploadPreTransformErrorWebhookEvent +- UploadPostTransformSuccessWebhookEvent +- UploadPostTransformErrorWebhookEvent - UnsafeUnwrapWebhookEvent - UnwrapWebhookEvent diff --git a/src/client.ts b/src/client.ts index 9c8c3e2e..0b020c23 100644 --- a/src/client.ts +++ b/src/client.ts @@ -29,6 +29,10 @@ import { import { UnsafeUnwrapWebhookEvent, UnwrapWebhookEvent, + UploadPostTransformErrorWebhookEvent, + UploadPostTransformSuccessWebhookEvent, + UploadPreTransformErrorWebhookEvent, + UploadPreTransformSuccessWebhookEvent, VideoTransformationAcceptedEvent, VideoTransformationErrorEvent, VideoTransformationReadyEvent, @@ -874,6 +878,10 @@ export declare namespace ImageKit { type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent as VideoTransformationErrorEvent, type VideoTransformationReadyEvent as VideoTransformationReadyEvent, + type UploadPreTransformSuccessWebhookEvent as UploadPreTransformSuccessWebhookEvent, + type UploadPreTransformErrorWebhookEvent as UploadPreTransformErrorWebhookEvent, + type UploadPostTransformSuccessWebhookEvent as UploadPostTransformSuccessWebhookEvent, + type UploadPostTransformErrorWebhookEvent as UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent as UnwrapWebhookEvent, }; diff --git a/src/resources/index.ts b/src/resources/index.ts index eb7f8ff3..ee190dcc 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -48,6 +48,10 @@ export { type VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent, type VideoTransformationReadyEvent, + type UploadPreTransformSuccessWebhookEvent, + type UploadPreTransformErrorWebhookEvent, + type UploadPostTransformSuccessWebhookEvent, + type UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent, } from './webhooks'; diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 1c7f6936..5d038e0e 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -25,7 +25,10 @@ export interface ImageOverlay extends BaseOverlay { /** * Array of transformations to be applied to the overlay image. Supported - * transformations depends on the base/parent asset. + * transformations depends on the base/parent asset. See overlays on + * [Images](https://imagekit.io/docs/add-overlays-on-images#list-of-supported-image-transformations-in-image-layers) + * and + * [Videos](https://imagekit.io/docs/add-overlays-on-videos#list-of-transformations-supported-on-image-overlay). */ transformation?: Array; } @@ -33,6 +36,8 @@ export interface ImageOverlay extends BaseOverlay { /** * Specifies an overlay to be applied on the parent image or video. ImageKit * supports overlays including images, text, videos, subtitles, and solid colors. + * See + * [Overlay using layers](https://imagekit.io/docs/transformations#overlay-using-layers). */ export type Overlay = TextOverlay | ImageOverlay | VideoOverlay | SubtitleOverlay | SolidColorOverlay; @@ -55,14 +60,18 @@ export interface OverlayPosition { /** * Specifies the x-coordinate of the top-left corner of the base asset where the * overlay's top-left corner will be positioned. It also accepts arithmetic - * expressions such as `bw_mul_0.4` or `bw_sub_cw`. Maps to `lx` in the URL. + * expressions such as `bw_mul_0.4` or `bw_sub_cw`. Maps to `lx` in the URL. Learn + * about + * [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). */ x?: number | string; /** * Specifies the y-coordinate of the top-left corner of the base asset where the * overlay's top-left corner will be positioned. It also accepts arithmetic - * expressions such as `bh_mul_0.4` or `bh_sub_ch`. Maps to `ly` in the URL. + * expressions such as `bh_mul_0.4` or `bh_sub_ch`. Maps to `ly` in the URL. Learn + * about + * [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). */ y?: number | string; } @@ -107,45 +116,60 @@ export interface SolidColorOverlay extends BaseOverlay { /** * Control width and height of the solid color overlay. Supported transformations - * depend on the base/parent asset. + * depend on the base/parent asset. See overlays on + * [Images](https://imagekit.io/docs/add-overlays-on-images#apply-transformation-on-solid-color-overlay) + * and + * [Videos](https://imagekit.io/docs/add-overlays-on-videos#apply-transformations-on-solid-color-block-overlay). */ transformation?: Array; } export interface SolidColorOverlayTransformation { /** - * Alpha transparency level + * Specifies the transparency level of the solid color overlay. Accepts integers + * from `1` to `9`. */ alpha?: number; /** - * Background color + * Specifies the background color of the solid color overlay. Accepts an RGB hex + * code (e.g., `FF0000`), an RGBA code (e.g., `FFAABB50`), or a color name. */ background?: string; /** - * Gradient effect for the overlay + * Creates a linear gradient with two colors. Pass `true` for a default gradient, + * or provide a string for a custom gradient. Only works if the base asset is an + * image. See + * [gradient](https://imagekit.io/docs/effects-and-enhancements#gradient---e-gradient). */ gradient?: true | string; /** - * Height of the solid color overlay + * Controls the height of the solid color overlay. Accepts a numeric value or an + * arithmetic expression. Learn about + * [arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). */ height?: number | string; /** - * Corner radius of the solid color overlay + * Specifies the corner radius of the solid color overlay. Set to `max` for + * circular or oval shape. See + * [radius](https://imagekit.io/docs/effects-and-enhancements#radius---r). */ radius?: number | 'max'; /** - * Width of the solid color overlay + * Controls the width of the solid color overlay. Accepts a numeric value or an + * arithmetic expression (e.g., `bw_mul_0.2` or `bh_div_2`). Learn about + * [arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). */ width?: number | string; } /** - * Options for generating ImageKit URLs with transformations + * Options for generating ImageKit URLs with transformations. See the + * [Transformations guide](https://imagekit.io/docs/transformations). */ export interface SrcOptions { /** @@ -171,20 +195,23 @@ export interface SrcOptions { /** * An array of objects specifying the transformations to be applied in the URL. If * more than one transformation is specified, they are applied in the order they - * are specified as chained transformations. + * are specified as chained transformations. See + * [Chained transformations](https://imagekit.io/docs/transformations#chained-transformations). */ transformation?: Array; /** * By default, the transformation string is added as a query parameter in the URL, * e.g., `?tr=w-100,h-100`. If you want to add the transformation string in the - * path of the URL, set this to `path`. + * path of the URL, set this to `path`. Learn more in the + * [Transformations guide](https://imagekit.io/docs/transformations). */ transformationPosition?: TransformationPosition; } /** - * Available streaming resolutions for adaptive bitrate streaming + * Available streaming resolutions for + * [adaptive bitrate streaming](https://imagekit.io/docs/adaptive-bitrate-streaming) */ export type StreamingResolution = '240' | '360' | '480' | '720' | '1080' | '1440' | '2160'; @@ -206,44 +233,75 @@ export interface SubtitleOverlay extends BaseOverlay { encoding?: 'auto' | 'plain' | 'base64'; /** - * Control styling of the subtitle. + * Control styling of the subtitle. See + * [Styling subtitles](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer). */ transformation?: Array; } +/** + * Subtitle styling options. + * [Learn more](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) + * from the docs. + */ export interface SubtitleOverlayTransformation { /** - * Background color for subtitles + * Specifies the subtitle background color using a standard color name, an RGB + * color code (e.g., FF0000), or an RGBA color code (e.g., FFAABB50). + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ background?: string; /** - * Text color for subtitles + * Sets the font color of the subtitle text using a standard color name, an RGB + * color code (e.g., FF0000), or an RGBA color code (e.g., FFAABB50). + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ color?: string; /** - * Font family for subtitles + * Font family for subtitles. Refer to the + * [supported fonts](https://imagekit.io/docs/add-overlays-on-images#supported-text-font-list). */ fontFamily?: string; /** - * Font outline for subtitles + * Sets the font outline of the subtitle text. Requires the outline width (an + * integer) and the outline color (as an RGB color code, RGBA color code, or + * standard web color name) separated by an underscore. Example: `fol-2_blue` + * (outline width of 2px and outline color blue), `fol-2_A1CCDD` (outline width of + * 2px and outline color `#A1CCDD`) and `fol-2_A1CCDD50` (outline width of 2px and + * outline color `#A1CCDD` at 50% opacity). + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ fontOutline?: string; /** - * Font shadow for subtitles + * Sets the font shadow for the subtitle text. Requires the shadow color (as an RGB + * color code, RGBA color code, or standard web color name) and shadow indent (an + * integer) separated by an underscore. Example: `fsh-blue_2` (shadow color blue, + * indent of 2px), `fsh-A1CCDD_3` (shadow color `#A1CCDD`, indent of 3px), + * `fsh-A1CCDD50_3` (shadow color `#A1CCDD` at 50% opacity, indent of 3px). + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ fontShadow?: string; /** - * Font size for subtitles + * Sets the font size of subtitle text. + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ - fontSize?: number | string; + fontSize?: number; /** - * Typography style for subtitles + * Sets the typography style of the subtitle text. Supports values are `b` for + * bold, `i` for italics, and `b_i` for bold with italics. + * + * [Subtitle styling options](https://imagekit.io/docs/add-overlays-on-videos#styling-controls-for-subtitles-layer) */ typography?: 'b' | 'i' | 'b_i'; } @@ -267,7 +325,8 @@ export interface TextOverlay extends BaseOverlay { encoding?: 'auto' | 'plain' | 'base64'; /** - * Control styling of the text overlay. + * Control styling of the text overlay. See + * [Text overlays](https://imagekit.io/docs/add-overlays-on-images#text-overlay). */ transformation?: Array; } @@ -298,7 +357,10 @@ export interface TextOverlayTransformation { /** * Specifies the font family of the overlaid text. Choose from the supported fonts - * list or use a custom font. + * list or use a custom font. See + * [Supported fonts](https://imagekit.io/docs/add-overlays-on-images#supported-text-font-list) + * and + * [Custom font](https://imagekit.io/docs/add-overlays-on-images#change-font-family-in-text-overlay). */ fontFamily?: string; @@ -315,7 +377,10 @@ export interface TextOverlayTransformation { innerAlignment?: 'left' | 'right' | 'center'; /** - * Specifies the line height of the text overlay. + * Specifies the line height of the text overlay. Accepts integer values + * representing line height in points. It can also accept + * [arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations) + * such as `bw_mul_0.2`, or `bh_div_20`. */ lineHeight?: number | string; @@ -339,15 +404,19 @@ export interface TextOverlayTransformation { rotation?: number | string; /** - * Specifies the typography style of the text. Supported values: `b` for bold, `i` - * for italics, and `b_i` for bold with italics. + * Specifies the typography style of the text. Supported values: + * + * - Single styles: `b` (bold), `i` (italic), `strikethrough`. + * - Combinations: Any combination separated by underscores, e.g., `b_i`, + * `b_i_strikethrough`. */ - typography?: 'b' | 'i' | 'b_i'; + typography?: string; /** * Specifies the maximum width (in pixels) of the overlaid text. The text wraps * automatically, and arithmetic expressions (e.g., `bw_mul_0.2` or `bh_div_2`) are - * supported. Useful when used in conjunction with the `background`. + * supported. Useful when used in conjunction with the `background`. Learn about + * [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). */ width?: number | string; } @@ -357,13 +426,15 @@ export interface TextOverlayTransformation { * converted to the corresponding transformation string before being added to the * URL. SDKs are updated regularly to support new transformations. If you want to * use a transformation that is not supported by the SDK, You can use the `raw` - * parameter to pass the transformation string directly. + * parameter to pass the transformation string directly. See the + * [Transformations documentation](https://imagekit.io/docs/transformations). */ export interface Transformation { /** * Uses AI to change the background. Provide a text prompt or a base64-encoded * prompt, e.g., `prompt-snow road` or `prompte-[urlencoded_base64_encoded_text]`. - * Not supported inside overlay. + * Not supported inside overlay. See + * [AI Change Background](https://imagekit.io/docs/ai-transformations#change-background-e-changebg). */ aiChangeBackground?: string; @@ -372,31 +443,44 @@ export interface Transformation { * removed background. Optionally, control the direction, elevation, and saturation * of the light source (e.g., `az-45` to change light direction). Pass `true` for * the default drop shadow, or provide a string for a custom drop shadow. Supported - * inside overlay. + * inside overlay. See + * [AI Drop Shadow](https://imagekit.io/docs/ai-transformations#ai-drop-shadow-e-dropshadow). */ aiDropShadow?: true | string; /** - * Applies ImageKit's in-house background removal. Supported inside overlay. + * Uses AI to edit images based on a text prompt. Provide a text prompt or a + * base64-encoded prompt, e.g., `prompt-snow road` or + * `prompte-[urlencoded_base64_encoded_text]`. Not supported inside overlay. + * See [AI Edit](https://imagekit.io/docs/ai-transformations#edit-image-e-edit). + */ + aiEdit?: string; + + /** + * Applies ImageKit's in-house background removal. Supported inside overlay. See + * [AI Background Removal](https://imagekit.io/docs/ai-transformations#imagekit-background-removal-e-bgremove). */ aiRemoveBackground?: true; /** * Uses third-party background removal. Note: It is recommended to use * aiRemoveBackground, ImageKit's in-house solution, which is more cost-effective. - * Supported inside overlay. + * Supported inside overlay. See + * [External Background Removal](https://imagekit.io/docs/ai-transformations#background-removal-e-removedotbg). */ aiRemoveBackgroundExternal?: true; /** * Performs AI-based retouching to improve faces or product shots. Not supported - * inside overlay. + * inside overlay. See + * [AI Retouch](https://imagekit.io/docs/ai-transformations#retouch-e-retouch). */ aiRetouch?: true; /** * Upscales images beyond their original dimensions using AI. Not supported inside - * overlay. + * overlay. See + * [AI Upscale](https://imagekit.io/docs/ai-transformations#upscale-e-upscale). */ aiUpscale?: true; @@ -404,19 +488,22 @@ export interface Transformation { * Generates a variation of an image using AI. This produces a new image with * slight variations from the original, such as changes in color, texture, and * other visual elements, while preserving the structure and essence of the - * original image. Not supported inside overlay. + * original image. Not supported inside overlay. See + * [AI Generate Variations](https://imagekit.io/docs/ai-transformations#generate-variations-of-an-image-e-genvar). */ aiVariation?: true; /** * Specifies the aspect ratio for the output, e.g., "ar-4-3". Typically used with * either width or height (but not both). For example: aspectRatio = `4:3`, `4_3`, - * or an expression like `iar_div_2`. + * or an expression like `iar_div_2`. See + * [Image resize and crop – Aspect ratio](https://imagekit.io/docs/image-resize-and-crop#aspect-ratio---ar). */ aspectRatio?: number | string; /** - * Specifies the audio codec, e.g., `aac`, `opus`, or `none`. + * Specifies the audio codec, e.g., `aac`, `opus`, or `none`. See + * [Audio codec](https://imagekit.io/docs/video-optimization#audio-codec---ac). */ audioCodec?: 'aac' | 'opus' | 'none'; @@ -424,84 +511,103 @@ export interface Transformation { * Specifies the background to be used in conjunction with certain cropping * strategies when resizing an image. * - * - A solid color: e.g., `red`, `F3F3F3`, `AAFF0010`. - * - A blurred background: e.g., `blurred`, `blurred_25_N15`, etc. + * - A solid color: e.g., `red`, `F3F3F3`, `AAFF0010`. See + * [Solid color background](https://imagekit.io/docs/effects-and-enhancements#solid-color-background). + * - A blurred background: e.g., `blurred`, `blurred_25_N15`, etc. See + * [Blurred background](https://imagekit.io/docs/effects-and-enhancements#blurred-background). * - Expand the image boundaries using generative fill: `genfill`. Not supported * inside overlay. Optionally, control the background scene by passing a text * prompt: `genfill[:-prompt-${text}]` or - * `genfill[:-prompte-${urlencoded_base64_encoded_text}]`. + * `genfill[:-prompte-${urlencoded_base64_encoded_text}]`. See + * [Generative fill background](https://imagekit.io/docs/ai-transformations#generative-fill-bg-genfill). */ background?: string; /** * Specifies the Gaussian blur level. Accepts an integer value between 1 and 100, - * or an expression like `bl-10`. + * or an expression like `bl-10`. See + * [Blur](https://imagekit.io/docs/effects-and-enhancements#blur---bl). */ blur?: number; /** * Adds a border to the output media. Accepts a string in the format * `_` (e.g., `5_FFF000` for a 5px yellow border), or an - * expression like `ih_div_20_FF00FF`. + * expression like `ih_div_20_FF00FF`. See + * [Border](https://imagekit.io/docs/effects-and-enhancements#border---b). */ border?: string; /** - * Indicates whether the output image should retain the original color profile. + * Indicates whether the output image should retain the original color profile. See + * [Color profile](https://imagekit.io/docs/image-optimization#color-profile---cp). */ colorProfile?: boolean; /** - * Automatically enhances the contrast of an image (contrast stretch). + * Automatically enhances the contrast of an image (contrast stretch). See + * [Contrast Stretch](https://imagekit.io/docs/effects-and-enhancements#contrast-stretch---e-contrast). */ contrastStretch?: true; /** - * Crop modes for image resizing + * Crop modes for image resizing. See + * [Crop modes & focus](https://imagekit.io/docs/image-resize-and-crop#crop-crop-modes--focus). */ crop?: 'force' | 'at_max' | 'at_max_enlarge' | 'at_least' | 'maintain_ratio'; /** - * Additional crop modes for image resizing + * Additional crop modes for image resizing. See + * [Crop modes & focus](https://imagekit.io/docs/image-resize-and-crop#crop-crop-modes--focus). */ cropMode?: 'pad_resize' | 'extract' | 'pad_extract'; /** * Specifies a fallback image if the resource is not found, e.g., a URL or file - * path. + * path. See + * [Default image](https://imagekit.io/docs/image-transformation#default-image---di). */ defaultImage?: string; /** * Accepts values between 0.1 and 5, or `auto` for automatic device pixel ratio - * (DPR) calculation. + * (DPR) calculation. See + * [DPR](https://imagekit.io/docs/image-resize-and-crop#dpr---dpr). */ dpr?: number; /** * Specifies the duration (in seconds) for trimming videos, e.g., `5` or `10.5`. * Typically used with startOffset to indicate the length from the start offset. - * Arithmetic expressions are supported. + * Arithmetic expressions are supported. See + * [Trim videos – Duration](https://imagekit.io/docs/trim-videos#duration---du). */ duration?: number | string; /** * Specifies the end offset (in seconds) for trimming videos, e.g., `5` or `10.5`. * Typically used with startOffset to define a time window. Arithmetic expressions - * are supported. + * are supported. See + * [Trim videos – End offset](https://imagekit.io/docs/trim-videos#end-offset---eo). */ endOffset?: number | string; /** * Flips or mirrors an image either horizontally, vertically, or both. Acceptable * values: `h` (horizontal), `v` (vertical), `h_v` (horizontal and vertical), or - * `v_h`. + * `v_h`. See [Flip](https://imagekit.io/docs/effects-and-enhancements#flip---fl). */ flip?: 'h' | 'v' | 'h_v' | 'v_h'; /** - * This parameter can be used with pad resize, maintain ratio, or extract crop to - * modify the padding or cropping behavior. + * Refines padding and cropping behavior for pad resize, maintain ratio, and + * extract crop modes. Supports manual positions and coordinate-based focus. With + * AI-based cropping, you can automatically keep key subjects in frame—such as + * faces or detected objects (e.g., `fo-face`, `fo-person`, `fo-car`)— while + * resizing. + * + * - See [Focus](https://imagekit.io/docs/image-resize-and-crop#focus---fo). + * - [Object aware cropping](https://imagekit.io/docs/image-resize-and-crop#object-aware-cropping---fo-object-name) */ focus?: string; @@ -510,72 +616,89 @@ export interface Transformation { * `mp4`, or `auto`. You can also pass `orig` for images to return the original * format. ImageKit automatically delivers images and videos in the optimal format * based on device support unless overridden by the dashboard settings or the - * format parameter. + * format parameter. See + * [Image format](https://imagekit.io/docs/image-optimization#format---f) and + * [Video format](https://imagekit.io/docs/video-optimization#format---f). */ format?: 'auto' | 'webp' | 'jpg' | 'jpeg' | 'png' | 'gif' | 'svg' | 'mp4' | 'webm' | 'avif' | 'orig'; /** * Creates a linear gradient with two colors. Pass `true` for a default gradient, - * or provide a string for a custom gradient. + * or provide a string for a custom gradient. See + * [Gradient](https://imagekit.io/docs/effects-and-enhancements#gradient---e-gradient). */ gradient?: true | string; /** - * Enables a grayscale effect for images. + * Enables a grayscale effect for images. See + * [Grayscale](https://imagekit.io/docs/effects-and-enhancements#grayscale---e-grayscale). */ grayscale?: true; /** * Specifies the height of the output. If a value between 0 and 1 is provided, it * is treated as a percentage (e.g., `0.5` represents 50% of the original height). - * You can also supply arithmetic expressions (e.g., `ih_mul_0.5`). + * You can also supply arithmetic expressions (e.g., `ih_mul_0.5`). Height + * transformation – + * [Images](https://imagekit.io/docs/image-resize-and-crop#height---h) · + * [Videos](https://imagekit.io/docs/video-resize-and-crop#height---h) */ height?: number | string; /** * Specifies whether the output image (in JPEG or PNG) should be compressed - * losslessly. + * losslessly. See + * [Lossless compression](https://imagekit.io/docs/image-optimization#lossless-webp-and-png---lo). */ lossless?: boolean; /** * By default, ImageKit removes all metadata during automatic image compression. - * Set this to true to preserve metadata. + * Set this to true to preserve metadata. See + * [Image metadata](https://imagekit.io/docs/image-optimization#image-metadata---md). */ metadata?: boolean; /** - * Named transformation reference + * Named transformation reference. See + * [Named transformations](https://imagekit.io/docs/transformations#named-transformations). */ named?: string; /** - * Specifies the opacity level of the output image. + * Specifies the opacity level of the output image. See + * [Opacity](https://imagekit.io/docs/effects-and-enhancements#opacity---o). */ opacity?: number; /** * If set to true, serves the original file without applying any transformations. + * See + * [Deliver original file as-is](https://imagekit.io/docs/core-delivery-features#deliver-original-file-as-is---orig-true). */ original?: boolean; /** * Specifies an overlay to be applied on the parent image or video. ImageKit * supports overlays including images, text, videos, subtitles, and solid colors. + * See + * [Overlay using layers](https://imagekit.io/docs/transformations#overlay-using-layers). */ overlay?: Overlay; /** * Extracts a specific page or frame from multi-page or layered files (PDF, PSD, * AI). For example, specify by number (e.g., `2`), a range (e.g., `3-4` for the - * 2nd and 3rd layers), or by name (e.g., `name-layer-4` for a PSD layer). + * 2nd and 3rd layers), or by name (e.g., `name-layer-4` for a PSD layer). See + * [Thumbnail extraction](https://imagekit.io/docs/vector-and-animated-images#get-thumbnail-from-psd-pdf-ai-eps-and-animated-files). */ page?: number | string; /** * Specifies whether the output JPEG image should be rendered progressively. * Progressive loading begins with a low-quality, pixelated version of the full - * image, which gradually improves to provide a faster perceived load time. + * image, which gradually improves to provide a faster perceived load time. See + * [Progressive images](https://imagekit.io/docs/image-optimization#progressive-image---pr). */ progressive?: boolean; @@ -583,12 +706,14 @@ export interface Transformation { * Specifies the quality of the output image for lossy formats such as JPEG, WebP, * and AVIF. A higher quality value results in a larger file size with better * quality, while a lower value produces a smaller file size with reduced quality. + * See [Quality](https://imagekit.io/docs/image-optimization#quality---q). */ quality?: number; /** - * Specifies the corner radius for rounded corners (e.g., 20) or `max` for - * circular/oval shapes. + * Specifies the corner radius for rounded corners (e.g., 20) or `max` for circular + * or oval shape. See + * [Radius](https://imagekit.io/docs/effects-and-enhancements#radius---r). */ radius?: number | 'max'; @@ -602,83 +727,98 @@ export interface Transformation { * Specifies the rotation angle in degrees. Positive values rotate the image * clockwise; you can also use, for example, `N40` for counterclockwise rotation or * `auto` to use the orientation specified in the image's EXIF data. For videos, - * only the following values are supported: 0, 90, 180, 270, or 360. + * only the following values are supported: 0, 90, 180, 270, or 360. See + * [Rotate](https://imagekit.io/docs/effects-and-enhancements#rotate---rt). */ rotation?: number | string; /** * Adds a shadow beneath solid objects in an image with a transparent background. * For AI-based drop shadows, refer to aiDropShadow. Pass `true` for a default - * shadow, or provide a string for a custom shadow. + * shadow, or provide a string for a custom shadow. See + * [Shadow](https://imagekit.io/docs/effects-and-enhancements#shadow---e-shadow). */ shadow?: true | string; /** * Sharpens the input image, highlighting edges and finer details. Pass `true` for - * default sharpening, or provide a numeric value for custom sharpening. + * default sharpening, or provide a numeric value for custom sharpening. See + * [Sharpen](https://imagekit.io/docs/effects-and-enhancements#sharpen---e-sharpen). */ sharpen?: true | number; /** * Specifies the start offset (in seconds) for trimming videos, e.g., `5` or - * `10.5`. Arithmetic expressions are also supported. + * `10.5`. Arithmetic expressions are also supported. See + * [Trim videos – Start offset](https://imagekit.io/docs/trim-videos#start-offset---so). */ startOffset?: number | string; /** * An array of resolutions for adaptive bitrate streaming, e.g., [`240`, `360`, - * `480`, `720`, `1080`]. + * `480`, `720`, `1080`]. See + * [Adaptive Bitrate Streaming](https://imagekit.io/docs/adaptive-bitrate-streaming). */ streamingResolutions?: Array; /** * Useful for images with a solid or nearly solid background and a central object. * This parameter trims the background, leaving only the central object in the - * output image. + * output image. See + * [Trim edges](https://imagekit.io/docs/effects-and-enhancements#trim-edges---t). */ trim?: true | number; /** * Applies Unsharp Masking (USM), an image sharpening technique. Pass `true` for a - * default unsharp mask, or provide a string for a custom unsharp mask. + * default unsharp mask, or provide a string for a custom unsharp mask. See + * [Unsharp Mask](https://imagekit.io/docs/effects-and-enhancements#unsharp-mask---e-usm). */ unsharpMask?: true | string; /** - * Specifies the video codec, e.g., `h264`, `vp9`, `av1`, or `none`. + * Specifies the video codec, e.g., `h264`, `vp9`, `av1`, or `none`. See + * [Video codec](https://imagekit.io/docs/video-optimization#video-codec---vc). */ videoCodec?: 'h264' | 'vp9' | 'av1' | 'none'; /** * Specifies the width of the output. If a value between 0 and 1 is provided, it is * treated as a percentage (e.g., `0.4` represents 40% of the original width). You - * can also supply arithmetic expressions (e.g., `iw_div_2`). + * can also supply arithmetic expressions (e.g., `iw_div_2`). Width transformation + * – [Images](https://imagekit.io/docs/image-resize-and-crop#width---w) · + * [Videos](https://imagekit.io/docs/video-resize-and-crop#width---w) */ width?: number | string; /** - * Focus using cropped image coordinates - X coordinate + * Focus using cropped image coordinates - X coordinate. See + * [Focus using cropped coordinates](https://imagekit.io/docs/image-resize-and-crop#example---focus-using-cropped-image-coordinates). */ x?: number | string; /** - * Focus using cropped image coordinates - X center coordinate + * Focus using cropped image coordinates - X center coordinate. See + * [Focus using cropped coordinates](https://imagekit.io/docs/image-resize-and-crop#example---focus-using-cropped-image-coordinates). */ xCenter?: number | string; /** - * Focus using cropped image coordinates - Y coordinate + * Focus using cropped image coordinates - Y coordinate. See + * [Focus using cropped coordinates](https://imagekit.io/docs/image-resize-and-crop#example---focus-using-cropped-image-coordinates). */ y?: number | string; /** - * Focus using cropped image coordinates - Y center coordinate + * Focus using cropped image coordinates - Y center coordinate. See + * [Focus using cropped coordinates](https://imagekit.io/docs/image-resize-and-crop#example---focus-using-cropped-image-coordinates). */ yCenter?: number | string; /** * Accepts a numeric value that determines how much to zoom in or out of the * cropped area. It should be used in conjunction with fo-face or fo-. + * See [Zoom](https://imagekit.io/docs/image-resize-and-crop#zoom---z). */ zoom?: number; } @@ -686,7 +826,8 @@ export interface Transformation { /** * By default, the transformation string is added as a query parameter in the URL, * e.g., `?tr=w-100,h-100`. If you want to add the transformation string in the - * path of the URL, set this to `path`. + * path of the URL, set this to `path`. Learn more in the + * [Transformations guide](https://imagekit.io/docs/transformations). */ export type TransformationPosition = 'path' | 'query'; @@ -709,7 +850,8 @@ export interface VideoOverlay extends BaseOverlay { /** * Array of transformation to be applied to the overlay video. Except - * `streamingResolutions`, all other video transformations are supported. + * `streamingResolutions`, all other video transformations are supported. See + * [Video transformations](https://imagekit.io/docs/video-transformation). */ transformation?: Array; } diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index 770f5a12..2bc5c914 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; +import * as FilesAPI from './files/files'; import { Webhook } from 'standardwebhooks'; export class Webhooks extends APIResource { @@ -22,16 +23,27 @@ export class Webhooks extends APIResource { } } +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ export interface VideoTransformationAcceptedEvent { /** * Unique identifier for the event. */ id: string; + /** + * Timestamp when the event was created in ISO8601 format. + */ created_at: string; data: VideoTransformationAcceptedEvent.Data; + /** + * Information about the original request that triggered the video transformation. + */ request: VideoTransformationAcceptedEvent.Request; type: 'video.transformation.accepted'; @@ -39,72 +51,134 @@ export interface VideoTransformationAcceptedEvent { export namespace VideoTransformationAcceptedEvent { export interface Data { + /** + * Information about the source video asset being transformed. + */ asset: Data.Asset; + /** + * Base information about a video transformation request. + */ transformation: Data.Transformation; } export namespace Data { + /** + * Information about the source video asset being transformed. + */ export interface Asset { /** - * Source asset URL. + * URL to download or access the source video file. */ url: string; } + /** + * Base information about a video transformation request. + */ export interface Transformation { + /** + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video + */ type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + /** + * Configuration options for video transformations. + */ options?: Transformation.Options; } export namespace Transformation { + /** + * Configuration options for video transformations. + */ export interface Options { + /** + * Audio codec used for encoding (aac or opus). + */ audio_codec?: 'aac' | 'opus'; + /** + * Whether to automatically rotate the video based on metadata. + */ auto_rotate?: boolean; + /** + * Output format for the transformed video or thumbnail. + */ format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + /** + * Quality setting for the output video. + */ quality?: number; + /** + * Streaming protocol for adaptive bitrate streaming. + */ stream_protocol?: 'HLS' | 'DASH'; + /** + * Array of quality representations for adaptive bitrate streaming. + */ variants?: Array; + /** + * Video codec used for encoding (h264 or vp9). + */ video_codec?: 'h264' | 'vp9'; } } } + /** + * Information about the original request that triggered the video transformation. + */ export interface Request { /** - * URL of the submitted request. + * Full URL of the transformation request that was submitted. */ url: string; /** - * Unique ID for the originating request. + * Unique identifier for the originating transformation request. */ x_request_id: string; /** - * User-Agent header of the originating request. + * User-Agent header from the original request that triggered the transformation. */ user_agent?: string; } } +/** + * Triggered when an error occurs during video encoding. Listen to this webhook to + * log error reasons and debug issues. Check your origin and URL endpoint settings + * if the reason is related to download failure. For other errors, contact ImageKit + * support. + */ export interface VideoTransformationErrorEvent { /** * Unique identifier for the event. */ id: string; + /** + * Timestamp when the event was created in ISO8601 format. + */ created_at: string; data: VideoTransformationErrorEvent.Data; + /** + * Information about the original request that triggered the video transformation. + */ request: VideoTransformationErrorEvent.Request; type: 'video.transformation.error'; @@ -112,190 +186,1305 @@ export interface VideoTransformationErrorEvent { export namespace VideoTransformationErrorEvent { export interface Data { + /** + * Information about the source video asset being transformed. + */ asset: Data.Asset; transformation: Data.Transformation; } export namespace Data { + /** + * Information about the source video asset being transformed. + */ export interface Asset { /** - * Source asset URL. + * URL to download or access the source video file. */ url: string; } export interface Transformation { + /** + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video + */ type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + /** + * Details about the transformation error. + */ error?: Transformation.Error; + /** + * Configuration options for video transformations. + */ options?: Transformation.Options; } export namespace Transformation { + /** + * Details about the transformation error. + */ export interface Error { + /** + * Specific reason for the transformation failure: + * + * - `encoding_failed`: Error during video encoding process + * - `download_failed`: Could not download source video + * - `internal_server_error`: Unexpected server error + */ reason: 'encoding_failed' | 'download_failed' | 'internal_server_error'; } + /** + * Configuration options for video transformations. + */ export interface Options { + /** + * Audio codec used for encoding (aac or opus). + */ audio_codec?: 'aac' | 'opus'; + /** + * Whether to automatically rotate the video based on metadata. + */ auto_rotate?: boolean; + /** + * Output format for the transformed video or thumbnail. + */ format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + /** + * Quality setting for the output video. + */ quality?: number; + /** + * Streaming protocol for adaptive bitrate streaming. + */ stream_protocol?: 'HLS' | 'DASH'; + /** + * Array of quality representations for adaptive bitrate streaming. + */ variants?: Array; + /** + * Video codec used for encoding (h264 or vp9). + */ video_codec?: 'h264' | 'vp9'; } } } + /** + * Information about the original request that triggered the video transformation. + */ export interface Request { /** - * URL of the submitted request. + * Full URL of the transformation request that was submitted. */ url: string; /** - * Unique ID for the originating request. + * Unique identifier for the originating transformation request. */ x_request_id: string; /** - * User-Agent header of the originating request. + * User-Agent header from the original request that triggered the transformation. */ user_agent?: string; } } +/** + * Triggered when video encoding is finished and the transformed resource is ready + * to be served. This is the key event to listen for - update your database or CMS + * flags when you receive this so your application can start showing the + * transformed video to users. + */ export interface VideoTransformationReadyEvent { /** * Unique identifier for the event. */ id: string; + /** + * Timestamp when the event was created in ISO8601 format. + */ created_at: string; data: VideoTransformationReadyEvent.Data; + /** + * Information about the original request that triggered the video transformation. + */ request: VideoTransformationReadyEvent.Request; type: 'video.transformation.ready'; + /** + * Performance metrics for the transformation process. + */ timings?: VideoTransformationReadyEvent.Timings; } export namespace VideoTransformationReadyEvent { export interface Data { + /** + * Information about the source video asset being transformed. + */ asset: Data.Asset; transformation: Data.Transformation; } export namespace Data { + /** + * Information about the source video asset being transformed. + */ export interface Asset { /** - * Source asset URL. + * URL to download or access the source video file. */ url: string; } export interface Transformation { + /** + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video + */ type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + /** + * Configuration options for video transformations. + */ options?: Transformation.Options; + /** + * Information about the transformed output video. + */ output?: Transformation.Output; } export namespace Transformation { + /** + * Configuration options for video transformations. + */ export interface Options { + /** + * Audio codec used for encoding (aac or opus). + */ audio_codec?: 'aac' | 'opus'; + /** + * Whether to automatically rotate the video based on metadata. + */ auto_rotate?: boolean; + /** + * Output format for the transformed video or thumbnail. + */ format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + /** + * Quality setting for the output video. + */ quality?: number; + /** + * Streaming protocol for adaptive bitrate streaming. + */ stream_protocol?: 'HLS' | 'DASH'; + /** + * Array of quality representations for adaptive bitrate streaming. + */ variants?: Array; + /** + * Video codec used for encoding (h264 or vp9). + */ video_codec?: 'h264' | 'vp9'; } + /** + * Information about the transformed output video. + */ export interface Output { + /** + * URL to access the transformed video. + */ url: string; + /** + * Metadata of the output video file. + */ video_metadata?: Output.VideoMetadata; } export namespace Output { + /** + * Metadata of the output video file. + */ export interface VideoMetadata { + /** + * Bitrate of the output video in bits per second. + */ bitrate: number; + /** + * Duration of the output video in seconds. + */ duration: number; + /** + * Height of the output video in pixels. + */ height: number; + /** + * Width of the output video in pixels. + */ width: number; } } } } + /** + * Information about the original request that triggered the video transformation. + */ export interface Request { /** - * URL of the submitted request. + * Full URL of the transformation request that was submitted. */ url: string; /** - * Unique ID for the originating request. + * Unique identifier for the originating transformation request. */ x_request_id: string; /** - * User-Agent header of the originating request. + * User-Agent header from the original request that triggered the transformation. */ user_agent?: string; } + /** + * Performance metrics for the transformation process. + */ export interface Timings { /** - * Milliseconds spent downloading the source. + * Time spent downloading the source video from your origin or media library, in + * milliseconds. */ download_duration?: number; /** - * Milliseconds spent encoding. + * Time spent encoding the video, in milliseconds. */ encoding_duration?: number; } } -export type UnsafeUnwrapWebhookEvent = - | VideoTransformationAcceptedEvent - | VideoTransformationReadyEvent - | VideoTransformationErrorEvent; +/** + * Triggered when a pre-transformation completes successfully. The file has been + * processed with the requested transformation and is now available in the Media + * Library. + */ +export interface UploadPreTransformSuccessWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + /** + * Object containing details of a successful upload. + */ + data: UploadPreTransformSuccessWebhookEvent.Data; + + request: UploadPreTransformSuccessWebhookEvent.Request; + + type: 'upload.pre-transform.success'; +} + +export namespace UploadPreTransformSuccessWebhookEvent { + /** + * Object containing details of a successful upload. + */ + export interface Data { + /** + * An array of tags assigned to the uploaded file by auto tagging. + */ + AITags?: Array | null; + + /** + * The audio codec used in the video (only for video). + */ + audioCodec?: string; + + /** + * The bit rate of the video in kbps (only for video). + */ + bitRate?: number; + + /** + * Value of custom coordinates associated with the image in the format + * `x,y,width,height`. If `customCoordinates` are not defined, then it is `null`. + * Send `customCoordinates` in `responseFields` in API request to get the value of + * this field. + */ + customCoordinates?: string | null; + + /** + * A key-value data associated with the asset. Use `responseField` in API request + * to get `customMetadata` in the upload API response. Before setting any custom + * metadata on an asset, you have to create the field using custom metadata fields + * API. Send `customMetadata` in `responseFields` in API request to get the value + * of this field. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. Can be set by the user or + * the ai-auto-description extension. + */ + description?: string; + + /** + * The duration of the video in seconds (only for video). + */ + duration?: number; + + /** + * Consolidated embedded metadata associated with the file. It includes exif, iptc, + * and xmp data. Send `embeddedMetadata` in `responseFields` in API request to get + * embeddedMetadata in the upload API response. + */ + embeddedMetadata?: { [key: string]: unknown }; + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + extensionStatus?: Data.ExtensionStatus; + + /** + * Unique fileId. Store this fileld in your database, as this will be used to + * perform update action on this file. + */ + fileId?: string; + + /** + * The relative path of the file in the media library e.g. + * `/marketing-assets/new-banner.jpg`. + */ + filePath?: string; + + /** + * Type of the uploaded file. Possible values are `image`, `non-image`. + */ + fileType?: string; + + /** + * Height of the image in pixels (Only for images) + */ + height?: number; + + /** + * Is the file marked as private. It can be either `true` or `false`. Send + * `isPrivateFile` in `responseFields` in API request to get the value of this + * field. + */ + isPrivateFile?: boolean; + + /** + * Is the file published or in draft state. It can be either `true` or `false`. + * Send `isPublished` in `responseFields` in API request to get the value of this + * field. + */ + isPublished?: boolean; + + /** + * Legacy metadata. Send `metadata` in `responseFields` in API request to get + * metadata in the upload API response. + */ + metadata?: FilesAPI.Metadata; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Size of the image file in Bytes. + */ + size?: number; + + /** + * The array of tags associated with the asset. If no tags are set, it will be + * `null`. Send `tags` in `responseFields` in API request to get the value of this + * field. + */ + tags?: Array | null; + + /** + * In the case of an image, a small thumbnail URL. + */ + thumbnailUrl?: string; + + /** + * A publicly accessible URL of the file. + */ + url?: string; + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + versionInfo?: Data.VersionInfo; + + /** + * The video codec used in the video (only for video). + */ + videoCodec?: string; + + /** + * Width of the image in pixels (Only for Images) + */ + width?: number; + } + + export namespace Data { + export interface AITag { + /** + * Confidence score of the tag. + */ + confidence?: number; + + /** + * Name of the tag. + */ + name?: string; + + /** + * Array of `AITags` associated with the image. If no `AITags` are set, it will be + * null. These tags can be added using the `google-auto-tagging` or + * `aws-auto-tagging` extensions. + */ + source?: string; + } + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + export interface ExtensionStatus { + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'remove-bg'?: 'success' | 'pending' | 'failed'; + } + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + export interface VersionInfo { + /** + * Unique identifier of the file version. + */ + id?: string; + + /** + * Name of the file version. + */ + name?: string; + } + } + + export interface Request { + /** + * The requested pre-transformation string. + */ + transformation: string; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } +} + +/** + * Triggered when a pre-transformation fails. The file upload may have been + * accepted, but the requested transformation could not be applied. + */ +export interface UploadPreTransformErrorWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPreTransformErrorWebhookEvent.Data; + + request: UploadPreTransformErrorWebhookEvent.Request; + + type: 'upload.pre-transform.error'; +} + +export namespace UploadPreTransformErrorWebhookEvent { + export interface Data { + /** + * Name of the file. + */ + name: string; + + /** + * Path of the file. + */ + path: string; + + transformation: Data.Transformation; + } + + export namespace Data { + export interface Transformation { + error: Transformation.Error; + } + + export namespace Transformation { + export interface Error { + /** + * Reason for the pre-transformation failure. + */ + reason: string; + } + } + } + + export interface Request { + /** + * The requested pre-transformation string. + */ + transformation: string; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } +} + +/** + * Triggered when a post-transformation completes successfully. The transformed + * version of the file is now ready and can be accessed via the provided URL. Note + * that each post-transformation generates a separate webhook event. + */ +export interface UploadPostTransformSuccessWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPostTransformSuccessWebhookEvent.Data; + + request: UploadPostTransformSuccessWebhookEvent.Request; + type: 'upload.post-transform.success'; +} + +export namespace UploadPostTransformSuccessWebhookEvent { + export interface Data { + /** + * Unique identifier of the originally uploaded file. + */ + fileId: string; + + /** + * Name of the file. + */ + name: string; + + /** + * URL of the generated post-transformation. + */ + url: string; + } + + export interface Request { + transformation: Request.Transformation; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } + + export namespace Request { + export interface Transformation { + /** + * Type of the requested post-transformation. + */ + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + + /** + * Only applicable if transformation type is 'abs'. Streaming protocol used. + */ + protocol?: 'hls' | 'dash'; + + /** + * Value for the requested transformation type. + */ + value?: string; + } + } +} + +/** + * Triggered when a post-transformation fails. The original file remains available, + * but the requested transformation could not be generated. + */ +export interface UploadPostTransformErrorWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPostTransformErrorWebhookEvent.Data; + + request: UploadPostTransformErrorWebhookEvent.Request; + + type: 'upload.post-transform.error'; +} + +export namespace UploadPostTransformErrorWebhookEvent { + export interface Data { + /** + * Unique identifier of the originally uploaded file. + */ + fileId: string; + + /** + * Name of the file. + */ + name: string; + + /** + * Path of the file. + */ + path: string; + + transformation: Data.Transformation; + + /** + * URL of the attempted post-transformation. + */ + url: string; + } + + export namespace Data { + export interface Transformation { + error: Transformation.Error; + } + + export namespace Transformation { + export interface Error { + /** + * Reason for the post-transformation failure. + */ + reason: string; + } + } + } + + export interface Request { + transformation: Request.Transformation; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } + + export namespace Request { + export interface Transformation { + /** + * Type of the requested post-transformation. + */ + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + + /** + * Only applicable if transformation type is 'abs'. Streaming protocol used. + */ + protocol?: 'hls' | 'dash'; + + /** + * Value for the requested transformation type. + */ + value?: string; + } + } +} + +/** + * Triggered when a pre-transformation completes successfully. The file has been + * processed with the requested transformation and is now available in the Media + * Library. + */ +export interface UploadPreTransformSuccessWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + /** + * Object containing details of a successful upload. + */ + data: UploadPreTransformSuccessWebhookEvent.Data; + + request: UploadPreTransformSuccessWebhookEvent.Request; + + type: 'upload.pre-transform.success'; +} + +export namespace UploadPreTransformSuccessWebhookEvent { + /** + * Object containing details of a successful upload. + */ + export interface Data { + /** + * An array of tags assigned to the uploaded file by auto tagging. + */ + AITags?: Array | null; + + /** + * The audio codec used in the video (only for video). + */ + audioCodec?: string; + + /** + * The bit rate of the video in kbps (only for video). + */ + bitRate?: number; + + /** + * Value of custom coordinates associated with the image in the format + * `x,y,width,height`. If `customCoordinates` are not defined, then it is `null`. + * Send `customCoordinates` in `responseFields` in API request to get the value of + * this field. + */ + customCoordinates?: string | null; + + /** + * A key-value data associated with the asset. Use `responseField` in API request + * to get `customMetadata` in the upload API response. Before setting any custom + * metadata on an asset, you have to create the field using custom metadata fields + * API. Send `customMetadata` in `responseFields` in API request to get the value + * of this field. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. Can be set by the user or + * the ai-auto-description extension. + */ + description?: string; + + /** + * The duration of the video in seconds (only for video). + */ + duration?: number; + + /** + * Consolidated embedded metadata associated with the file. It includes exif, iptc, + * and xmp data. Send `embeddedMetadata` in `responseFields` in API request to get + * embeddedMetadata in the upload API response. + */ + embeddedMetadata?: { [key: string]: unknown }; + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + extensionStatus?: Data.ExtensionStatus; + + /** + * Unique fileId. Store this fileld in your database, as this will be used to + * perform update action on this file. + */ + fileId?: string; + + /** + * The relative path of the file in the media library e.g. + * `/marketing-assets/new-banner.jpg`. + */ + filePath?: string; + + /** + * Type of the uploaded file. Possible values are `image`, `non-image`. + */ + fileType?: string; + + /** + * Height of the image in pixels (Only for images) + */ + height?: number; + + /** + * Is the file marked as private. It can be either `true` or `false`. Send + * `isPrivateFile` in `responseFields` in API request to get the value of this + * field. + */ + isPrivateFile?: boolean; + + /** + * Is the file published or in draft state. It can be either `true` or `false`. + * Send `isPublished` in `responseFields` in API request to get the value of this + * field. + */ + isPublished?: boolean; + + /** + * Legacy metadata. Send `metadata` in `responseFields` in API request to get + * metadata in the upload API response. + */ + metadata?: FilesAPI.Metadata; + + /** + * Name of the asset. + */ + name?: string; + + /** + * Size of the image file in Bytes. + */ + size?: number; + + /** + * The array of tags associated with the asset. If no tags are set, it will be + * `null`. Send `tags` in `responseFields` in API request to get the value of this + * field. + */ + tags?: Array | null; + + /** + * In the case of an image, a small thumbnail URL. + */ + thumbnailUrl?: string; + + /** + * A publicly accessible URL of the file. + */ + url?: string; + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + versionInfo?: Data.VersionInfo; + + /** + * The video codec used in the video (only for video). + */ + videoCodec?: string; + + /** + * Width of the image in pixels (Only for Images) + */ + width?: number; + } + + export namespace Data { + export interface AITag { + /** + * Confidence score of the tag. + */ + confidence?: number; + + /** + * Name of the tag. + */ + name?: string; + + /** + * Array of `AITags` associated with the image. If no `AITags` are set, it will be + * null. These tags can be added using the `google-auto-tagging` or + * `aws-auto-tagging` extensions. + */ + source?: string; + } + + /** + * Extension names with their processing status at the time of completion of the + * request. It could have one of the following status values: + * + * `success`: The extension has been successfully applied. `failed`: The extension + * has failed and will not be retried. `pending`: The extension will finish + * processing in some time. On completion, the final status (success / failed) will + * be sent to the `webhookUrl` provided. + * + * If no extension was requested, then this parameter is not returned. + */ + export interface ExtensionStatus { + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; + + 'remove-bg'?: 'success' | 'pending' | 'failed'; + } + + /** + * An object containing the file or file version's `id` (versionId) and `name`. + */ + export interface VersionInfo { + /** + * Unique identifier of the file version. + */ + id?: string; + + /** + * Name of the file version. + */ + name?: string; + } + } + + export interface Request { + /** + * The requested pre-transformation string. + */ + transformation: string; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } +} + +/** + * Triggered when a pre-transformation fails. The file upload may have been + * accepted, but the requested transformation could not be applied. + */ +export interface UploadPreTransformErrorWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPreTransformErrorWebhookEvent.Data; + + request: UploadPreTransformErrorWebhookEvent.Request; + + type: 'upload.pre-transform.error'; +} + +export namespace UploadPreTransformErrorWebhookEvent { + export interface Data { + /** + * Name of the file. + */ + name: string; + + /** + * Path of the file. + */ + path: string; + + transformation: Data.Transformation; + } + + export namespace Data { + export interface Transformation { + error: Transformation.Error; + } + + export namespace Transformation { + export interface Error { + /** + * Reason for the pre-transformation failure. + */ + reason: string; + } + } + } + + export interface Request { + /** + * The requested pre-transformation string. + */ + transformation: string; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } +} + +/** + * Triggered when a post-transformation completes successfully. The transformed + * version of the file is now ready and can be accessed via the provided URL. Note + * that each post-transformation generates a separate webhook event. + */ +export interface UploadPostTransformSuccessWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPostTransformSuccessWebhookEvent.Data; + + request: UploadPostTransformSuccessWebhookEvent.Request; + + type: 'upload.post-transform.success'; +} + +export namespace UploadPostTransformSuccessWebhookEvent { + export interface Data { + /** + * Unique identifier of the originally uploaded file. + */ + fileId: string; + + /** + * Name of the file. + */ + name: string; + + /** + * URL of the generated post-transformation. + */ + url: string; + } + + export interface Request { + transformation: Request.Transformation; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } + + export namespace Request { + export interface Transformation { + /** + * Type of the requested post-transformation. + */ + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + + /** + * Only applicable if transformation type is 'abs'. Streaming protocol used. + */ + protocol?: 'hls' | 'dash'; + + /** + * Value for the requested transformation type. + */ + value?: string; + } + } +} + +/** + * Triggered when a post-transformation fails. The original file remains available, + * but the requested transformation could not be generated. + */ +export interface UploadPostTransformErrorWebhookEvent { + /** + * Unique identifier for the event. + */ + id: string; + + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; + + data: UploadPostTransformErrorWebhookEvent.Data; + + request: UploadPostTransformErrorWebhookEvent.Request; + + type: 'upload.post-transform.error'; +} + +export namespace UploadPostTransformErrorWebhookEvent { + export interface Data { + /** + * Unique identifier of the originally uploaded file. + */ + fileId: string; + + /** + * Name of the file. + */ + name: string; + + /** + * Path of the file. + */ + path: string; + + transformation: Data.Transformation; + + /** + * URL of the attempted post-transformation. + */ + url: string; + } + + export namespace Data { + export interface Transformation { + error: Transformation.Error; + } + + export namespace Transformation { + export interface Error { + /** + * Reason for the post-transformation failure. + */ + reason: string; + } + } + } + + export interface Request { + transformation: Request.Transformation; + + /** + * Unique identifier for the originating request. + */ + x_request_id: string; + } + + export namespace Request { + export interface Transformation { + /** + * Type of the requested post-transformation. + */ + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + + /** + * Only applicable if transformation type is 'abs'. Streaming protocol used. + */ + protocol?: 'hls' | 'dash'; + + /** + * Value for the requested transformation type. + */ + value?: string; + } + } +} + +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ +export type UnsafeUnwrapWebhookEvent = + | VideoTransformationAcceptedEvent + | VideoTransformationReadyEvent + | VideoTransformationErrorEvent + | UploadPreTransformSuccessWebhookEvent + | UploadPreTransformErrorWebhookEvent + | UploadPostTransformSuccessWebhookEvent + | UploadPostTransformErrorWebhookEvent; + +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ export type UnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent - | VideoTransformationErrorEvent; + | VideoTransformationErrorEvent + | UploadPreTransformSuccessWebhookEvent + | UploadPreTransformErrorWebhookEvent + | UploadPostTransformSuccessWebhookEvent + | UploadPostTransformErrorWebhookEvent; export declare namespace Webhooks { export { type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent as VideoTransformationErrorEvent, type VideoTransformationReadyEvent as VideoTransformationReadyEvent, + type UploadPreTransformSuccessWebhookEvent as UploadPreTransformSuccessWebhookEvent, + type UploadPreTransformErrorWebhookEvent as UploadPreTransformErrorWebhookEvent, + type UploadPostTransformSuccessWebhookEvent as UploadPostTransformSuccessWebhookEvent, + type UploadPostTransformErrorWebhookEvent as UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent as UnwrapWebhookEvent, }; From c1bc59ba35af6b0e7bac82e1e87e3937eda72cf1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 08:48:51 +0000 Subject: [PATCH 07/73] feat(api): manual updates --- .stats.yml | 4 ++-- src/resources/webhooks.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index caed6f5a..66a8df1f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0cdbdd05084a8219bc003a34670472f198cf91e5f6402cede2cb1094b7bfd98d.yml -openapi_spec_hash: d337c89146f41fa215560bb5aef2e4ec +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-a49f0e337789b1a4368194ed004ac4c1b0c0cd2ce4344e14546422632242d897.yml +openapi_spec_hash: a7f3999c6227aac108cd80253ffc7730 config_hash: 249ee22f294858ab0971b8379f7cb519 diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index 2bc5c914..94e52cfb 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -129,9 +129,9 @@ export namespace VideoTransformationAcceptedEvent { variants?: Array; /** - * Video codec used for encoding (h264 or vp9). + * Video codec used for encoding (h264, vp9, or av1). */ - video_codec?: 'h264' | 'vp9'; + video_codec?: 'h264' | 'vp9' | 'av1'; } } } @@ -277,9 +277,9 @@ export namespace VideoTransformationErrorEvent { variants?: Array; /** - * Video codec used for encoding (h264 or vp9). + * Video codec used for encoding (h264, vp9, or av1). */ - video_codec?: 'h264' | 'vp9'; + video_codec?: 'h264' | 'vp9' | 'av1'; } } } @@ -416,9 +416,9 @@ export namespace VideoTransformationReadyEvent { variants?: Array; /** - * Video codec used for encoding (h264 or vp9). + * Video codec used for encoding (h264, vp9, or av1). */ - video_codec?: 'h264' | 'vp9'; + video_codec?: 'h264' | 'vp9' | 'av1'; } /** From 6a0ff10cdb0a7d8578bfdb03ff0694dac6f01e1f Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 15:09:54 +0530 Subject: [PATCH 08/73] base --- src/client.ts | 1 + src/resources/helper.ts | 25 +++++++++++++ src/resources/index.ts | 1 + tests/api-resources/helper.test.ts | 57 ++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/resources/helper.ts create mode 100644 tests/api-resources/helper.test.ts diff --git a/src/client.ts b/src/client.ts index 0b020c23..04797821 100644 --- a/src/client.ts +++ b/src/client.ts @@ -806,6 +806,7 @@ export class ImageKit { accounts: API.Accounts = new API.Accounts(this); beta: API.Beta = new API.Beta(this); webhooks: API.Webhooks = new API.Webhooks(this); + helper: API.Helper = new API.Helper(this); } ImageKit.CustomMetadataFields = CustomMetadataFields; diff --git a/src/resources/helper.ts b/src/resources/helper.ts new file mode 100644 index 00000000..0d7948a6 --- /dev/null +++ b/src/resources/helper.ts @@ -0,0 +1,25 @@ +// Helper resource for additional utility functions +// File manually created for helper functions - not generated + +import { APIResource } from '../core/resource'; +import type { ImageKit } from '../client'; + +export class Helper extends APIResource { + constructor(client: ImageKit) { + super(client); + } + + /** + * Build ImageKit URL - currently returns input as-is + */ + buildSrc(input: string): string { + return input; + } + + /** + * Build transformation string - currently returns input as-is + */ + buildTransformationString(input: string): string { + return input; + } +} diff --git a/src/resources/index.ts b/src/resources/index.ts index ee190dcc..dea7b1c2 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -55,3 +55,4 @@ export { type UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent, } from './webhooks'; +export { Helper } from './helper'; diff --git a/tests/api-resources/helper.test.ts b/tests/api-resources/helper.test.ts new file mode 100644 index 00000000..e96df48b --- /dev/null +++ b/tests/api-resources/helper.test.ts @@ -0,0 +1,57 @@ +// Test for the Helper resource following TDD approach + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource helper', () => { + describe('buildSrc', () => { + test('should exist as a method', () => { + expect(typeof client.helper.buildSrc).toBe('function'); + }); + + test('should return the input string as-is', () => { + const input = '/test-image.jpg'; + const result = client.helper.buildSrc(input); + + expect(result).toBe(input); + }); + }); + + describe('buildTransformationString', () => { + test('should exist as a method', () => { + expect(typeof client.helper.buildTransformationString).toBe('function'); + }); + + test('should return the input string as-is', () => { + const input = 'w-300,h-200'; + const result = client.helper.buildTransformationString(input); + + expect(result).toBe(input); + }); + + test('should handle different string inputs', () => { + const inputs = [ + 'w-300,h-200,c-at_max', + 'f-webp,q-80', + 'r-10,b-5_black', + '', + 'single-param' + ]; + + inputs.forEach(input => { + const result = client.helper.buildTransformationString(input); + expect(result).toBe(input); + }); + }); + + test('should handle empty string', () => { + const result = client.helper.buildTransformationString(''); + expect(result).toBe(''); + }); + }); +}); From 76fedd825365bfa3c14ab0acfaecc2699c3f8c20 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 15:41:32 +0530 Subject: [PATCH 09/73] Add unit tests for image transformations and overlays - Implement tests for buildTransformationString to validate transformation string generation. - Create comprehensive tests for overlay transformations, including text, image, video, subtitle, and solid color overlays. - Ensure proper handling of invalid overlay values and encoding scenarios. - Validate URL generation for various overlay types and nested transformations. --- src/lib/transformation-utils.ts | 123 ++ src/resources/helper.ts | 327 +++- tests/url-generation/basic.test.ts | 1428 +++++++++++++++++ .../buildTransformationString.test.ts | 33 + tests/url-generation/overlay.test.ts | 606 +++++++ 5 files changed, 2511 insertions(+), 6 deletions(-) create mode 100644 src/lib/transformation-utils.ts create mode 100644 tests/url-generation/basic.test.ts create mode 100644 tests/url-generation/buildTransformationString.test.ts create mode 100644 tests/url-generation/overlay.test.ts diff --git a/src/lib/transformation-utils.ts b/src/lib/transformation-utils.ts new file mode 100644 index 00000000..c1582346 --- /dev/null +++ b/src/lib/transformation-utils.ts @@ -0,0 +1,123 @@ +// Transformation utilities ported from JavaScript SDK +// This file is in src/lib/ to avoid conflicts with generated code + +import type { SrcOptions, TransformationPosition } from '../resources/shared'; + +const QUERY_TRANSFORMATION_POSITION: TransformationPosition = 'query'; +const PATH_TRANSFORMATION_POSITION: TransformationPosition = 'path'; +const CHAIN_TRANSFORM_DELIMITER: string = ':'; +const TRANSFORM_DELIMITER: string = ','; +const TRANSFORM_KEY_VALUE_DELIMITER: string = '-'; + +/** + * Supported transformations mapping + * {@link https://imagekit.io/docs/transformations} + */ +export const supportedTransforms: { [key: string]: string } = { + // Basic sizing & layout + width: 'w', + height: 'h', + aspectRatio: 'ar', + background: 'bg', + border: 'b', + crop: 'c', + cropMode: 'cm', + dpr: 'dpr', + focus: 'fo', + quality: 'q', + x: 'x', + xCenter: 'xc', + y: 'y', + yCenter: 'yc', + format: 'f', + videoCodec: 'vc', + audioCodec: 'ac', + radius: 'r', + rotation: 'rt', + blur: 'bl', + named: 'n', + defaultImage: 'di', + flip: 'fl', + original: 'orig', + startOffset: 'so', + endOffset: 'eo', + duration: 'du', + streamingResolutions: 'sr', + + // AI & advanced effects + grayscale: 'e-grayscale', + aiUpscale: 'e-upscale', + aiRetouch: 'e-retouch', + aiVariation: 'e-genvar', + aiDropShadow: 'e-dropshadow', + aiChangeBackground: 'e-changebg', + aiRemoveBackground: 'e-bgremove', + aiRemoveBackgroundExternal: 'e-removedotbg', + contrastStretch: 'e-contrast', + shadow: 'e-shadow', + sharpen: 'e-sharpen', + unsharpMask: 'e-usm', + gradient: 'e-gradient', + + // Other flags & finishing + progressive: 'pr', + lossless: 'lo', + colorProfile: 'cp', + metadata: 'md', + opacity: 'o', + trim: 't', + zoom: 'z', + page: 'pg', + + // Text overlay transformations + fontSize: 'fs', + fontFamily: 'ff', + fontColor: 'co', + innerAlignment: 'ia', + padding: 'pa', + alpha: 'al', + typography: 'tg', + lineHeight: 'lh', + + // Subtitles transformations + fontOutline: 'fol', + fontShadow: 'fsh', + + // Raw pass-through + raw: 'raw', + + // Additional missing mappings from JS SDK + aiEdit: 'e-edit', +}; + +export default { + addAsQueryParameter: (options: SrcOptions): boolean => { + return options.transformationPosition === QUERY_TRANSFORMATION_POSITION; + }, + getTransformKey: function (transform: string): string { + if (!transform) { + return ''; + } + + return supportedTransforms[transform] || supportedTransforms[transform.toLowerCase()] || ''; + }, + getChainTransformDelimiter: function (): string { + return CHAIN_TRANSFORM_DELIMITER; + }, + getTransformDelimiter: function (): string { + return TRANSFORM_DELIMITER; + }, + getTransformKeyValueDelimiter: function (): string { + return TRANSFORM_KEY_VALUE_DELIMITER; + }, +}; + +export const safeBtoa = function (str: string): string { + // Check if running in browser environment + if (typeof globalThis !== 'undefined' && 'btoa' in globalThis) { + return (globalThis as any).btoa(str); + } else { + // Node.js fallback + return Buffer.from(str, 'utf8').toString('base64'); + } +}; diff --git a/src/resources/helper.ts b/src/resources/helper.ts index 0d7948a6..3539e3e5 100644 --- a/src/resources/helper.ts +++ b/src/resources/helper.ts @@ -3,6 +3,258 @@ import { APIResource } from '../core/resource'; import type { ImageKit } from '../client'; +import type { + SrcOptions, + Transformation, + ImageOverlay, + TextOverlay, + VideoOverlay, + SubtitleOverlay, + SolidColorOverlay, +} from './shared'; +import transformationUtils, { safeBtoa } from '../lib/transformation-utils'; + +const TRANSFORMATION_PARAMETER = 'tr'; +const SIMPLE_OVERLAY_PATH_REGEX = new RegExp('^[a-zA-Z0-9-._/ ]*$'); +const SIMPLE_OVERLAY_TEXT_REGEX = new RegExp('^[a-zA-Z0-9-._ ]*$'); + +function removeTrailingSlash(str: string): string { + if (typeof str == 'string' && str[str.length - 1] == '/') { + str = str.substring(0, str.length - 1); + } + return str; +} + +function removeLeadingSlash(str: string): string { + if (typeof str == 'string' && str[0] == '/') { + str = str.slice(1); + } + return str; +} + +function pathJoin(parts: string[], sep?: string): string { + var separator = sep || '/'; + var replace = new RegExp(separator + '{1,}', 'g'); + return parts.join(separator).replace(replace, separator); +} + +function processInputPath(str: string, encoding: string): string { + // Remove leading and trailing slashes + str = removeTrailingSlash(removeLeadingSlash(str)); + if (encoding === 'plain') { + return `i-${str.replace(/\//g, '@@')}`; + } + if (encoding === 'base64') { + return `ie-${encodeURIComponent(safeBtoa(str))}`; + } + if (SIMPLE_OVERLAY_PATH_REGEX.test(str)) { + return `i-${str.replace(/\//g, '@@')}`; + } else { + return `ie-${encodeURIComponent(safeBtoa(str))}`; + } +} + +function processText(str: string, encoding: TextOverlay['encoding']): string { + if (encoding === 'plain') { + return `i-${encodeURIComponent(str)}`; + } + if (encoding === 'base64') { + return `ie-${encodeURIComponent(safeBtoa(str))}`; + } + if (SIMPLE_OVERLAY_TEXT_REGEX.test(str)) { + return `i-${encodeURIComponent(str)}`; + } + return `ie-${encodeURIComponent(safeBtoa(str))}`; +} + +function processOverlay(overlay: Transformation['overlay']): string | undefined { + const entries = []; + + const { type, position = {}, timing = {}, transformation = [] } = overlay || {}; + + if (!type) { + return; + } + + switch (type) { + case 'text': + { + const textOverlay = overlay as TextOverlay; + if (!textOverlay.text) { + return; + } + const encoding = textOverlay.encoding || 'auto'; + + entries.push('l-text'); + entries.push(processText(textOverlay.text, encoding)); + } + break; + case 'image': + entries.push('l-image'); + { + const imageOverlay = overlay as ImageOverlay; + const encoding = imageOverlay.encoding || 'auto'; + if (imageOverlay.input) { + entries.push(processInputPath(imageOverlay.input, encoding)); + } else { + return; + } + } + break; + case 'video': + entries.push('l-video'); + { + const videoOverlay = overlay as VideoOverlay; + const encoding = videoOverlay.encoding || 'auto'; + if (videoOverlay.input) { + entries.push(processInputPath(videoOverlay.input, encoding)); + } else { + return; + } + } + break; + case 'subtitle': + entries.push('l-subtitle'); + { + const subtitleOverlay = overlay as SubtitleOverlay; + const encoding = subtitleOverlay.encoding || 'auto'; + if (subtitleOverlay.input) { + entries.push(processInputPath(subtitleOverlay.input, encoding)); + } else { + return; + } + } + break; + case 'solidColor': + entries.push('l-image'); + entries.push(`i-ik_canvas`); + { + const solidColorOverlay = overlay as SolidColorOverlay; + if (solidColorOverlay.color) { + entries.push(`bg-${solidColorOverlay.color}`); + } else { + return; + } + } + break; + } + + const { x, y, focus } = position; + if (x) { + entries.push(`lx-${x}`); + } + if (y) { + entries.push(`ly-${y}`); + } + if (focus) { + entries.push(`lfo-${focus}`); + } + + const { start, end, duration } = timing; + if (start) { + entries.push(`lso-${start}`); + } + if (end) { + entries.push(`leo-${end}`); + } + if (duration) { + entries.push(`ldu-${duration}`); + } + + const transformationString = buildTransformationString(transformation); + + if (transformationString && transformationString.trim() !== '') { + entries.push(transformationString); + } + + entries.push('l-end'); + + return entries.join(transformationUtils.getTransformDelimiter()); +} + +function buildTransformationString(transformation: Transformation[] | undefined): string { + if (!Array.isArray(transformation)) { + return ''; + } + + var parsedTransforms: string[] = []; + for (var i = 0, l = transformation.length; i < l; i++) { + var parsedTransformStep: string[] = []; + const currentTransform = transformation[i]; + if (!currentTransform) continue; + + for (var key in currentTransform) { + let value = currentTransform[key as keyof Transformation]; + if (value === undefined || value === null) { + continue; + } + + if (key === 'overlay' && typeof value === 'object') { + var rawString = processOverlay(value as Transformation['overlay']); + if (rawString && rawString.trim() !== '') { + parsedTransformStep.push(rawString); + } + continue; // Always continue as overlay is processed. + } + + var transformKey = transformationUtils.getTransformKey(key); + if (!transformKey) { + transformKey = key; + } + + if (transformKey === '') { + continue; + } + + if ( + [ + 'e-grayscale', + 'e-contrast', + 'e-removedotbg', + 'e-bgremove', + 'e-upscale', + 'e-retouch', + 'e-genvar', + ].includes(transformKey) + ) { + if (value === true || value === '-' || value === 'true') { + parsedTransformStep.push(transformKey); + } else { + // Any other value means that the effect should not be applied + continue; + } + } else if ( + ['e-sharpen', 'e-shadow', 'e-gradient', 'e-usm', 'e-dropshadow'].includes(transformKey) && + (value.toString().trim() === '' || value === true || value === 'true') + ) { + parsedTransformStep.push(transformKey); + } else if (key === 'raw') { + parsedTransformStep.push(currentTransform[key] as string); + } else { + if (transformKey === 'di') { + value = removeTrailingSlash(removeLeadingSlash((value as string) || '')); + value = value.replace(/\//g, '@@'); + } + if (transformKey === 'sr' && Array.isArray(value)) { + value = value.join('_'); + } + // Special case for trim with empty string - should be treated as true + if (transformKey === 't' && value.toString().trim() === '') { + value = 'true'; + } + + parsedTransformStep.push( + [transformKey, value].join(transformationUtils.getTransformKeyValueDelimiter()), + ); + } + } + if (parsedTransformStep.length) { + parsedTransforms.push(parsedTransformStep.join(transformationUtils.getTransformDelimiter())); + } + } + + return parsedTransforms.join(transformationUtils.getChainTransformDelimiter()); +} export class Helper extends APIResource { constructor(client: ImageKit) { @@ -10,16 +262,79 @@ export class Helper extends APIResource { } /** - * Build ImageKit URL - currently returns input as-is + * Builds a source URL with the given options. + * + * @param opts - The options for building the source URL. + * @returns The constructed source URL. */ - buildSrc(input: string): string { - return input; + buildSrc(opts: SrcOptions): string { + opts.urlEndpoint = opts.urlEndpoint || ''; + opts.src = opts.src || ''; + opts.transformationPosition = opts.transformationPosition || 'query'; + + if (!opts.src) { + return ''; + } + + const isAbsoluteURL = opts.src.startsWith('http://') || opts.src.startsWith('https://'); + + var urlObj, isSrcParameterUsedForURL, urlEndpointPattern; + + try { + if (!isAbsoluteURL) { + urlEndpointPattern = new URL(opts.urlEndpoint).pathname; + urlObj = new URL(pathJoin([opts.urlEndpoint.replace(urlEndpointPattern, ''), opts.src])); + } else { + urlObj = new URL(opts.src!); + isSrcParameterUsedForURL = true; + } + } catch (e) { + console.error(e); + return ''; + } + + for (var i in opts.queryParameters) { + urlObj.searchParams.append(i, String(opts.queryParameters[i])); + } + + var transformationString = this.buildTransformationString(opts.transformation); + + if (transformationString && transformationString.length) { + if (!transformationUtils.addAsQueryParameter(opts) && !isSrcParameterUsedForURL) { + urlObj.pathname = pathJoin([ + TRANSFORMATION_PARAMETER + transformationUtils.getChainTransformDelimiter() + transformationString, + urlObj.pathname, + ]); + } + } + + if (urlEndpointPattern) { + urlObj.pathname = pathJoin([urlEndpointPattern, urlObj.pathname]); + } else { + urlObj.pathname = pathJoin([urlObj.pathname]); + } + + if (transformationString && transformationString.length) { + if (transformationUtils.addAsQueryParameter(opts) || isSrcParameterUsedForURL) { + if (urlObj.searchParams.toString() !== '') { + // In 12 node.js .size was not there. So, we need to check if it is an object or not. + return `${urlObj.href}&${TRANSFORMATION_PARAMETER}=${transformationString}`; + } else { + return `${urlObj.href}?${TRANSFORMATION_PARAMETER}=${transformationString}`; + } + } + } + + return urlObj.href; } /** - * Build transformation string - currently returns input as-is + * Builds a transformation string from the given transformations. + * + * @param transformation - The transformations to apply. + * @returns The constructed transformation string. */ - buildTransformationString(input: string): string { - return input; + buildTransformationString(transformation: Transformation[] | undefined): string { + return buildTransformationString(transformation); } } diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts new file mode 100644 index 00000000..c812ad47 --- /dev/null +++ b/tests/url-generation/basic.test.ts @@ -0,0 +1,1428 @@ +import { expect } from '@jest/globals'; +import ImageKit from '@imagekit/nodejs'; +import type { SrcOptions } from '../../src/resources/shared'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('URL generation', function () { + it('should return an empty string when src is not provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + } as SrcOptions); + + expect(url).toBe(''); + }); + + it('should return an empty string when src is /', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/', + }); + + expect(url).toBe('https://ik.imagekit.io/test_url_endpoint/'); + }); + + it('should return an empty string when src is invalid', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: 'https://', + }); + + expect(url).toBe(''); + }); + + it('should generate a valid URL when src is provided without transformation', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg`); + }); + + it('should generate a valid URL when a src is provided without transformation', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: 'https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg', + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg`); + }); + + it('should generate a valid URL when undefined transformation parameters are provided with path', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/test_path_alt.jpg', + transformationPosition: 'query', + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg`); + }); + + it('By default transformationPosition should be query', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + { + rotation: 90, + }, + ], + }); + expect(url).toBe( + 'https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90', + ); + }); + + it('should generate the URL without sdk version', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + transformationPosition: 'path', + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400/test_path.jpg`, + ); + }); + + it('should generate the correct URL with a valid src and transformation', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + // Now transformed URL goes into query since transformationPosition is "query". + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL when the provided path contains multiple leading slashes', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '///test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL when the urlEndpoint is overridden', function () { + const url = client.helper.buildSrc({ + // We do not override urlEndpoint here + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint_alt', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint_alt/test_path.jpg?tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL with transformationPosition as query parameter when src is provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/test_path.jpg', + transformationPosition: 'query', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL with a valid src parameter and transformation', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: 'https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL with transformationPosition as query parameter when src is provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: 'https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg', + transformationPosition: 'query', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`, + ); + }); + + it('should merge query parameters correctly in the generated URL', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: 'https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?t1=v1', + queryParameters: { t2: 'v2', t3: 'v3' }, + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?t1=v1&t2=v2&t3=v3&tr=h-300,w-400`, + ); + }); + + it('should generate the correct URL with chained transformations', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + { + rotation: '90', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90`, + ); + }); + + it('should generate the correct URL with chained transformations including a new undocumented transformation parameter', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + { + raw: 'rndm_trnsf-abcd', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rndm_trnsf-abcd`, + ); + }); + + it('should generate the correct URL when overlay image transformation is provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + raw: 'l-image,i-overlay.jpg,w-100,b-10_CDDC39,l-end', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,l-image,i-overlay.jpg,w-100,b-10_CDDC39,l-end`, + ); + }); + + it('should generate the correct URL when overlay image transformation contains a slash in the overlay path', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + raw: 'l-image,i-/path/to/overlay.jpg,w-100,b-10_CDDC39,l-end', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,l-image,i-/path/to/overlay.jpg,w-100,b-10_CDDC39,l-end`, + ); + }); + + it('should generate the correct URL when border transformation is applied', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + border: '20_FF0000', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,b-20_FF0000`, + ); + }); + + it('should generate the correct URL when transformation has empty key and value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + raw: '', + }, + ], + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg`); + }); + + it('should generate the correct URL when an undefined transform is provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + raw: 'undefined-transform-true', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=undefined-transform-true`, + ); + }); + + it('should generate the correct URL when transformation key has an empty value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + defaultImage: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=di-`, + ); + }); + + it("should generate the correct URL when transformation key has '-' as its value", function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + contrastStretch: '-' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=e-contrast`, + ); + }); + + it('should skip transformation parameters that are undefined or null', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + // quality: undefined, // Can't test this due to exactOptionalPropertyTypes + // contrastStretch: null, // Can't test this due to exactOptionalPropertyTypes + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`, + ); + }); + + it('should skip transformation parameters that are false', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + contrastStretch: false as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`, + ); + }); + + it('should include only the key when transformation value is an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + shadow: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,e-shadow`, + ); + }); + + it('should include both key and value when transformation parameter value is provided', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + shadow: 'bl-15_st-40_x-10_y-N5', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,e-shadow-bl-15_st-40_x-10_y-N5`, + ); + }); + + it('should generate the correct URL when trim transformation is set to true as a boolean', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + trim: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`, + ); + }); + + it('should generate the correct URL when trim transformation is set to true as a string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + defaultImage: '/test_path.jpg', + trim: 'true' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`, + ); + }); + + it('should generate the correct URL for AI background removal when set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackground: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`, + ); + }); + + it("should generate the correct URL for AI background removal when 'true' is provided as a string", function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackground: 'true' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`, + ); + }); + + it('should not apply AI background removal when value is not true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackground: 'false' as any, + }, + ], + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg`); + }); + + it('should generate the correct URL for external AI background removal when set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackgroundExternal: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`, + ); + }); + + it("should generate the correct URL for external AI background removal when 'true' is provided as a string", function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackgroundExternal: 'true' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`, + ); + }); + + it('should not apply external AI background removal when value is not true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiRemoveBackgroundExternal: 'false' as any, + }, + ], + }); + + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg`); + }); + + it('should generate the correct URL when gradient transformation is provided as a string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + gradient: 'ld-top_from-green_to-00FF0010_sp-1', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient-ld-top_from-green_to-00FF0010_sp-1`, + ); + }); + + it('should generate the correct URL when gradient transformation is provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + gradient: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`, + ); + }); + + it('should generate the correct URL when gradient transformation is set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + gradient: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`, + ); + }); + + it('should generate the correct URL when AI drop shadow transformation is set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiDropShadow: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`, + ); + }); + + it('should generate the correct URL when AI drop shadow transformation is provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiDropShadow: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`, + ); + }); + + it('should generate the correct URL when AI drop shadow transformation is provided with a specific string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aiDropShadow: 'az-45', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow-az-45`, + ); + }); + + it('should generate the correct URL when shadow transformation is set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + shadow: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`, + ); + }); + + it('should generate the correct URL when shadow transformation is provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + shadow: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`, + ); + }); + + it('should generate the correct URL when shadow transformation is provided with a specific string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + shadow: 'bl-15_st-40_x-10_y-N5', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow-bl-15_st-40_x-10_y-N5`, + ); + }); + + it('should generate the correct URL when sharpen transformation is set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + sharpen: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`, + ); + }); + + it('should generate the correct URL when sharpen transformation is provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + sharpen: '' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`, + ); + }); + + it('should generate the correct URL when sharpen transformation is provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + sharpen: 10, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen-10`, + ); + }); + + it('should generate the correct URL when unsharpMask transformation is set to true', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + unsharpMask: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`, + ); + }); + + it('should generate the correct URL when unsharpMask transformation is provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + unsharpMask: '', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`, + ); + }); + + it('should generate the correct URL when unsharpMask transformation is provided with a string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + unsharpMask: '2-2-0.8-0.024', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm-2-2-0.8-0.024`, + ); + }); + + it('should generate the correct URL for trim transformation when set to true (boolean)', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + trim: true, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`, + ); + }); + + it('should generate the correct URL for trim transformation when provided as an empty string', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + trim: '' as any, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`, + ); + }); + + it('should generate the correct URL for trim transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + trim: 5, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-5`, + ); + }); + + // Width parameter tests + it('should generate the correct URL for width transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + width: 400, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`, + ); + }); + + it('should generate the correct URL for width transformation when provided with a string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + width: '400', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`, + ); + }); + + it('should generate the correct URL for width transformation when provided with an arithmetic expression', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + width: 'iw_div_2', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-iw_div_2`, + ); + }); + + // Height parameter tests + it('should generate the correct URL for height transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + height: 300, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`, + ); + }); + + it('should generate the correct URL for height transformation when provided with a string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + height: '300', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`, + ); + }); + + it('should generate the correct URL for height transformation when provided with an arithmetic expression', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + height: 'ih_mul_0.5', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-ih_mul_0.5`, + ); + }); + + // AspectRatio parameter tests + it('should generate the correct URL for aspectRatio transformation when provided with a string value in colon format', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aspectRatio: '4:3', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4:3`, + ); + }); + + it('should generate the correct URL for aspectRatio transformation when provided with an alternate underscore format', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aspectRatio: '4_3', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4_3`, + ); + }); + + it('should generate the correct URL for aspectRatio transformation when provided with an arithmetic expression', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + aspectRatio: 'iar_div_2', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-iar_div_2`, + ); + }); + + // Background parameter tests + it('should generate the correct URL for background transformation when provided with a solid color', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + background: 'FF0000', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-FF0000`, + ); + }); + + it('should generate the correct URL for background transformation when provided with the blurred option', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + background: 'blurred', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-blurred`, + ); + }); + + it('should generate the correct URL for background transformation when provided with the genfill option', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + background: 'genfill', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-genfill`, + ); + }); + + // Crop parameter tests + it('should generate the correct URL for crop transformation when provided with force value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + crop: 'force', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-force`, + ); + }); + + it('should generate the correct URL for crop transformation when provided with at_max value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + crop: 'at_max', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-at_max`, + ); + }); + + // CropMode parameter tests + it('should generate the correct URL for cropMode transformation when provided with pad_resize', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + cropMode: 'pad_resize', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-pad_resize`, + ); + }); + + it('should generate the correct URL for cropMode transformation when provided with extract value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + cropMode: 'extract', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-extract`, + ); + }); + + // Focus parameter tests + it('should generate the correct URL for focus transformation when provided with a string value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + focus: 'center', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-center`, + ); + }); + + it('should generate the correct URL for focus transformation when face detection is specified', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + focus: 'face', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-face`, + ); + }); + + // Quality parameter test + it('should generate the correct URL for quality transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + quality: 80, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=q-80`, + ); + }); + + // Coordinate parameters tests + it('should generate the correct URL for x coordinate transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + x: 10, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=x-10`, + ); + }); + + it('should generate the correct URL for y coordinate transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + y: 20, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=y-20`, + ); + }); + + it('should generate the correct URL for xCenter transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + xCenter: 30, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=xc-30`, + ); + }); + + it('should generate the correct URL for yCenter transformation when provided with a number value', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path1.jpg', + transformation: [ + { + yCenter: 40, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=yc-40`, + ); + }); + + it('Including deprecated properties', function () { + // This is just testing how the SDK constructs the URL, not actual valid transformations. + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: 300, + width: 400, + aspectRatio: '4-3', + quality: 40, + crop: 'force', + cropMode: 'extract', + focus: 'left', + format: 'jpeg', + radius: 50, + background: 'A94D34', + border: '5-A94D34', + rotation: 90, + blur: 10, + named: 'some_name', + progressive: true, + lossless: true, + trim: 5, + metadata: true, + colorProfile: true, + defaultImage: '/folder/file.jpg/', + dpr: 3, + sharpen: 10, + unsharpMask: '2-2-0.8-0.024', + contrastStretch: true, + grayscale: true, + shadow: 'bl-15_st-40_x-10_y-N5', + gradient: 'from-red_to-white', + original: true, + raw: 'h-200,w-300,l-image,i-logo.png,l-end', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,e-sharpen-10,e-usm-2-2-0.8-0.024,e-contrast,e-grayscale,e-shadow-bl-15_st-40_x-10_y-N5,e-gradient-from-red_to-white,orig-true,h-200,w-300,l-image,i-logo.png,l-end`, + ); + }); + + it('should generate the correct URL with many transformations, including video and AI transforms', function () { + // Example test with comprehensive transformations + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'query', + src: '/test_path.jpg', + transformation: [ + { + height: 300, + width: 400, + aspectRatio: '4-3', + quality: 40, + crop: 'force', + cropMode: 'extract', + focus: 'left', + format: 'jpeg', + radius: 50, + background: 'A94D34', + border: '5-A94D34', + rotation: 90, + blur: 10, + named: 'some_name', + progressive: true, + lossless: true, + trim: 5, + metadata: true, + colorProfile: true, + defaultImage: '/folder/file.jpg/', + dpr: 3, + x: 10, + y: 20, + xCenter: 30, + yCenter: 40, + flip: 'h', + opacity: 0.8, + zoom: 2, + // Video transformations + videoCodec: 'h264', + audioCodec: 'aac', + startOffset: 5, + endOffset: 15, + duration: 10, + streamingResolutions: ['1440', '1080'], + // AI transformations + grayscale: true, + aiUpscale: true, + aiRetouch: true, + aiVariation: true, + aiDropShadow: true, + aiChangeBackground: 'prompt-car', + aiRemoveBackground: true, + contrastStretch: true, + shadow: 'bl-15_st-40_x-10_y-N5', + sharpen: 10, + unsharpMask: '2-2-0.8-0.024', + gradient: 'from-red_to-white', + original: true, + page: '2_4', + raw: 'h-200,w-300,l-image,i-logo.png,l-end', + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,x-10,y-20,xc-30,yc-40,fl-h,o-0.8,z-2,vc-h264,ac-aac,so-5,eo-15,du-10,sr-1440_1080,e-grayscale,e-upscale,e-retouch,e-genvar,e-dropshadow,e-changebg-prompt-car,e-bgremove,e-contrast,e-shadow-bl-15_st-40_x-10_y-N5,e-sharpen-10,e-usm-2-2-0.8-0.024,e-gradient-from-red_to-white,orig-true,pg-2_4,h-200,w-300,l-image,i-logo.png,l-end`, + ); + }); +}); diff --git a/tests/url-generation/buildTransformationString.test.ts b/tests/url-generation/buildTransformationString.test.ts new file mode 100644 index 00000000..bf50a5e3 --- /dev/null +++ b/tests/url-generation/buildTransformationString.test.ts @@ -0,0 +1,33 @@ +import { expect } from '@jest/globals'; +import ImageKit from '@imagekit/nodejs'; +import type { Transformation } from '../../src/resources/shared'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('buildTransformationString', function () { + it('should return an empty string when no transformations are provided', function () { + const result = client.helper.buildTransformationString([{}] as Transformation[]); + expect(result).toBe(''); + }); + + it('should generate a transformation string for width only', function () { + const result = client.helper.buildTransformationString([{ width: 300 }]); + expect(result).toBe('w-300'); + }); + + it('should generate a transformation string for multiple transformations', function () { + const result = client.helper.buildTransformationString([ + { + overlay: { + type: 'text', + text: 'Hello', + }, + }, + ]); + expect(result).toBe('l-text,i-Hello,l-end'); + }); +}); diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts new file mode 100644 index 00000000..385d5fa1 --- /dev/null +++ b/tests/url-generation/overlay.test.ts @@ -0,0 +1,606 @@ +import { expect } from '@jest/globals'; +import ImageKit from '@imagekit/nodejs'; +import { safeBtoa } from '../../src/lib/transformation-utils'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('Overlay Transformation Test Cases', function () { + it('Ignore invalid values if text is missing', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'text', + } as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Ignore if type is missing', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: {} as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Ignore invalid values if input (image)', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'image', + } as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Ignore invalid values if input (video)', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'video', + } as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Ignore invalid values if input (subtitle)', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'subtitle', + } as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Ignore invalid values if color is missing (solidColor)', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'solidColor', + } as any, + }, + ], + }); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/base-image.jpg`); + }); + + it('Text overlay generates correct URL with encoded overlay text', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'Minimal Text', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent('Minimal Text')},l-end/base-image.jpg`, + ); + }); + + it('Image overlay generates correct URL with input logo.png', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'image', + input: 'logo.png', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-image,i-logo.png,l-end/base-image.jpg`, + ); + }); + + it('Video overlay generates correct URL with input play-pause-loop.mp4', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-video.mp4', + transformation: [ + { + overlay: { + type: 'video', + input: 'play-pause-loop.mp4', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-video,i-play-pause-loop.mp4,l-end/base-video.mp4`, + ); + }); + + it('Subtitle overlay generates correct URL with input subtitle.srt', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-video.mp4', + transformation: [ + { + overlay: { + type: 'subtitle', + input: 'subtitle.srt', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-subtitle,i-subtitle.srt,l-end/base-video.mp4`, + ); + }); + + it('Solid color overlay generates correct URL with background color FF0000', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + overlay: { + type: 'solidColor', + color: 'FF0000', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-image,i-ik_canvas,bg-FF0000,l-end/base-image.jpg`, + ); + }); + + it('Combined overlay transformations generate correct URL including nested overlays', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + src: '/base-image.jpg', + transformation: [ + { + // Text overlay + overlay: { + type: 'text', + text: 'Every thing', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + fontSize: 20, + fontFamily: 'Arial', + fontColor: '0000ff', + innerAlignment: 'left', + padding: 5, + alpha: 7, + typography: 'b', + background: 'red', + radius: 10, + rotation: 'N45', + flip: 'h', + lineHeight: 20, + }, + ], + }, + }, + { + // Image overlay + overlay: { + type: 'image', + input: 'logo.png', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + height: 'bh_mul_0.5', + rotation: 'N45', + flip: 'h', + overlay: { + type: 'text', + text: 'Nested text overlay', + }, + }, + ], + }, + }, + { + // Video overlay. Just for URL generation testing, you can't actually overlay a video on an image. + overlay: { + type: 'video', + input: 'play-pause-loop.mp4', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + height: 'bh_mul_0.5', + rotation: 'N45', + flip: 'h', + }, + ], + }, + }, + { + // Subtitle overlay. Just for URL generation testing, you can't actually overlay a subtitle on an image. + overlay: { + type: 'subtitle', + input: 'subtitle.srt', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + fontSize: 12, + fontColor: 'white', + } as any, // Using any to allow general transformations in subtitle overlay for testing + ], + }, + }, + { + // Solid color overlay + overlay: { + type: 'solidColor', + color: 'FF0000', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 100, + height: 50, + // Using type assertion to allow general transformation params for testing + } as any, + ], + }, + }, + ], + }); + + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent('Every thing')},lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-${encodeURIComponent('Nested text overlay')},l-end,l-end:l-video,i-play-pause-loop.mp4,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-subtitle,i-subtitle.srt,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-image,i-ik_canvas,bg-FF0000,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end/base-image.jpg`, + ); + }); +}); + +describe('Overlay encoding test cases', function () { + it('Nested simple path, should use i instead of ie, handle slash properly', function () { + const url = client.helper.buildSrc({ + // Using a different endpoint here, as we are checking for /demo + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'image', + input: '/customer_logo/nykaa.png', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-image,i-customer_logo@@nykaa.png,l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + + it('Nested non-simple path, should use ie instead of i', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'image', + input: '/customer_logo/Ñykaa.png', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-image,ie-Y3VzdG9tZXJfbG9nby9OzIN5a2FhLnBuZw%3D%3D,l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + + it('Simple text overlay, should use i instead of ie', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'Manu', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,i-Manu,l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + + it('Simple text overlay with spaces and other safe characters, should use i instead of ie', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'alnum123-._ ', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,i-${encodeURIComponent('alnum123-._ ')},l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + + it('Non simple text overlay, should use ie instead of i', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: "Let's use ©, ®, ™, etc", + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,ie-TGV0J3MgdXNlIMKpLCDCriwg4oSiLCBldGM%3D,l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + + it('Text overlay with explicit plain encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'HelloWorld', + encoding: 'plain', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,i-HelloWorld,l-end/sample.jpg`, + ); + }); + + it('Text overlay with explicit base64 encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'HelloWorld', + encoding: 'base64', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,ie-${encodeURIComponent(safeBtoa('HelloWorld'))},l-end/sample.jpg`, + ); + }); + + it('Image overlay with explicit plain encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.jpg', + transformation: [ + { + overlay: { + type: 'image', + input: '/customer/logo.png', + encoding: 'plain', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-image,i-customer@@logo.png,l-end/sample.jpg`, + ); + }); + + it('Image overlay with explicit base64 encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.jpg', + transformation: [ + { + overlay: { + type: 'image', + input: '/customer/logo.png', + encoding: 'base64', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-image,ie-${encodeURIComponent(safeBtoa('customer/logo.png'))},l-end/sample.jpg`, + ); + }); + + it('Video overlay with explicit base64 encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.mp4', + transformation: [ + { + overlay: { + type: 'video', + input: '/path/to/video.mp4', + encoding: 'base64', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-video,ie-${encodeURIComponent(safeBtoa('path/to/video.mp4'))},l-end/sample.mp4`, + ); + }); + + it('Subtitle overlay with explicit plain encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.mp4', + transformation: [ + { + overlay: { + type: 'subtitle', + input: '/sub.srt', + encoding: 'plain', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-subtitle,i-sub.srt,l-end/sample.mp4`, + ); + }); + + it('Subtitle overlay with explicit base64 encoding', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.mp4', + transformation: [ + { + overlay: { + type: 'subtitle', + input: 'sub.srt', + encoding: 'base64', + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-subtitle,ie-${encodeURIComponent(safeBtoa('sub.srt'))},l-end/sample.mp4`, + ); + }); + + it('Avoid double encoding when transformation string is in query params', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/sample.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'Minimal Text', + }, + }, + ], + transformationPosition: 'query', + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/sample.jpg?tr=l-text,i-Minimal%20Text,l-end`, + ); + }); +}); From ef30e9c65b9259bbc5bef259a565789c1502dae8 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 15:42:30 +0530 Subject: [PATCH 10/73] refactor(tests): remove redundant helper tests --- tests/api-resources/helper.test.ts | 57 ------------------------------ 1 file changed, 57 deletions(-) delete mode 100644 tests/api-resources/helper.test.ts diff --git a/tests/api-resources/helper.test.ts b/tests/api-resources/helper.test.ts deleted file mode 100644 index e96df48b..00000000 --- a/tests/api-resources/helper.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Test for the Helper resource following TDD approach - -import ImageKit from '@imagekit/nodejs'; - -const client = new ImageKit({ - privateAPIKey: 'My Private API Key', - password: 'My Password', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource helper', () => { - describe('buildSrc', () => { - test('should exist as a method', () => { - expect(typeof client.helper.buildSrc).toBe('function'); - }); - - test('should return the input string as-is', () => { - const input = '/test-image.jpg'; - const result = client.helper.buildSrc(input); - - expect(result).toBe(input); - }); - }); - - describe('buildTransformationString', () => { - test('should exist as a method', () => { - expect(typeof client.helper.buildTransformationString).toBe('function'); - }); - - test('should return the input string as-is', () => { - const input = 'w-300,h-200'; - const result = client.helper.buildTransformationString(input); - - expect(result).toBe(input); - }); - - test('should handle different string inputs', () => { - const inputs = [ - 'w-300,h-200,c-at_max', - 'f-webp,q-80', - 'r-10,b-5_black', - '', - 'single-param' - ]; - - inputs.forEach(input => { - const result = client.helper.buildTransformationString(input); - expect(result).toBe(input); - }); - }); - - test('should handle empty string', () => { - const result = client.helper.buildTransformationString(''); - expect(result).toBe(''); - }); - }); -}); From e4adc14a0662f9782665bdff8865229819618995 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 16:38:15 +0530 Subject: [PATCH 11/73] refactor(transformation-utils): replace safeBtoa implementation with toBase64 utility; update overlay tests for consistency --- src/lib/transformation-utils.ts | 10 +- tests/url-generation/overlay.test.ts | 277 ++++++++++++++------------- 2 files changed, 142 insertions(+), 145 deletions(-) diff --git a/src/lib/transformation-utils.ts b/src/lib/transformation-utils.ts index c1582346..e4db32cc 100644 --- a/src/lib/transformation-utils.ts +++ b/src/lib/transformation-utils.ts @@ -2,6 +2,7 @@ // This file is in src/lib/ to avoid conflicts with generated code import type { SrcOptions, TransformationPosition } from '../resources/shared'; +import { toBase64 } from '../internal/utils/base64'; const QUERY_TRANSFORMATION_POSITION: TransformationPosition = 'query'; const PATH_TRANSFORMATION_POSITION: TransformationPosition = 'path'; @@ -113,11 +114,6 @@ export default { }; export const safeBtoa = function (str: string): string { - // Check if running in browser environment - if (typeof globalThis !== 'undefined' && 'btoa' in globalThis) { - return (globalThis as any).btoa(str); - } else { - // Node.js fallback - return Buffer.from(str, 'utf8').toString('base64'); - } + // Use the SDK's built-in base64 utility that properly handles different runtimes + return toBase64(str); }; diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts index 385d5fa1..f43ca11d 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/url-generation/overlay.test.ts @@ -204,147 +204,144 @@ describe('Overlay Transformation Test Cases', function () { urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', src: '/base-image.jpg', transformation: [ - { - // Text overlay - overlay: { - type: 'text', - text: 'Every thing', - position: { - x: '10', - y: '20', - focus: 'center', - }, - timing: { - start: 5, - duration: '10', - end: 15, - }, - transformation: [ - { - width: 'bw_mul_0.5', - fontSize: 20, - fontFamily: 'Arial', - fontColor: '0000ff', - innerAlignment: 'left', - padding: 5, - alpha: 7, - typography: 'b', - background: 'red', - radius: 10, - rotation: 'N45', - flip: 'h', - lineHeight: 20, - }, - ], - }, - }, - { - // Image overlay - overlay: { - type: 'image', - input: 'logo.png', - position: { - x: '10', - y: '20', - focus: 'center', - }, - timing: { - start: 5, - duration: '10', - end: 15, - }, - transformation: [ - { - width: 'bw_mul_0.5', - height: 'bh_mul_0.5', - rotation: 'N45', - flip: 'h', - overlay: { - type: 'text', - text: 'Nested text overlay', + { + // Text overlay + overlay: { + type: "text", + text: "Every thing", + position: { + x: "10", + y: "20", + focus: "center" + }, + timing: { + start: 5, + duration: "10", + end: 15 + }, + transformation: [{ + width: "bw_mul_0.5", + fontSize: 20, + fontFamily: "Arial", + fontColor: "0000ff", + innerAlignment: "left", + padding: 5, + alpha: 7, + typography: "b", + background: "red", + radius: 10, + rotation: "N45", + flip: "h", + lineHeight: 20 + }] + } }, - }, - ], - }, - }, - { - // Video overlay. Just for URL generation testing, you can't actually overlay a video on an image. - overlay: { - type: 'video', - input: 'play-pause-loop.mp4', - position: { - x: '10', - y: '20', - focus: 'center', - }, - timing: { - start: 5, - duration: '10', - end: 15, - }, - transformation: [ - { - width: 'bw_mul_0.5', - height: 'bh_mul_0.5', - rotation: 'N45', - flip: 'h', - }, - ], - }, - }, - { - // Subtitle overlay. Just for URL generation testing, you can't actually overlay a subtitle on an image. - overlay: { - type: 'subtitle', - input: 'subtitle.srt', - position: { - x: '10', - y: '20', - focus: 'center', - }, - timing: { - start: 5, - duration: '10', - end: 15, - }, - transformation: [ - { - fontSize: 12, - fontColor: 'white', - } as any, // Using any to allow general transformations in subtitle overlay for testing - ], - }, - }, - { - // Solid color overlay - overlay: { - type: 'solidColor', - color: 'FF0000', - position: { - x: '10', - y: '20', - focus: 'center', - }, - timing: { - start: 5, - duration: '10', - end: 15, - }, - transformation: [ - { - width: 100, - height: 50, - // Using type assertion to allow general transformation params for testing - } as any, - ], - }, - }, + { + // Image overlay + overlay: { + type: "image", + input: "logo.png", + position: { + x: "10", + y: "20", + focus: "center" + }, + timing: { + start: 5, + duration: "10", + end: 15 + }, + transformation: [ + { + width: "bw_mul_0.5", + height: "bh_mul_0.5", + rotation: "N45", + flip: "h", + overlay: { + type: "text", + text: "Nested text overlay", + } + } + ] + } + }, + { + // Video overlay. Just for URL generation testing, you can't actually overlay a video on an image. + overlay: { + type: "video", + input: "play-pause-loop.mp4", + position: { + x: "10", + y: "20", + focus: "center" + }, + timing: { + start: 5, + duration: "10", + end: 15 + }, + transformation: [{ + width: "bw_mul_0.5", + height: "bh_mul_0.5", + rotation: "N45", + flip: "h", + }] + } + }, + { + // Subtitle overlay. Just for URL generation testing, you can't actually overlay a subtitle on an image. + overlay: { + type: "subtitle", + input: "subtitle.srt", + position: { + x: "10", + y: "20", + focus: "center" + }, + timing: { + start: 5, + duration: "10", + end: 15 + }, + transformation: [{ + background: "red", + color: "0000ff", + fontFamily: "Arial", + fontOutline: "2_A1CCDD50", + fontShadow: "A1CCDD_3" + }] + } + }, + { + // Solid color overlay + overlay: { + type: "solidColor", + color: "FF0000", + position: { + x: "10", + y: "20", + focus: "center" + }, + timing: { + start: 5, + duration: "10", + end: 15 + }, + transformation: [{ + width: "bw_mul_0.5", + height: "bh_mul_0.5", + alpha: 0.5, + background: "red", + gradient: true, + radius: "max" + }] + } + } ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent('Every thing')},lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-${encodeURIComponent('Nested text overlay')},l-end,l-end:l-video,i-play-pause-loop.mp4,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-subtitle,i-subtitle.srt,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,l-end:l-image,i-ik_canvas,bg-FF0000,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end/base-image.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Every%20thing,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-Nested%20text%20overlay,l-end,l-end:l-video,i-play-pause-loop.mp4,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end:l-subtitle,i-subtitle.srt,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,bg-red,color-0000ff,ff-Arial,fol-2_A1CCDD50,fsh-A1CCDD_3,l-end:l-image,i-ik_canvas,bg-FF0000,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,al-0.5,bg-red,e-gradient,r-max,l-end/base-image.jpg`); + }); }); @@ -383,8 +380,12 @@ describe('Overlay encoding test cases', function () { }, ], }); + + // Buffer.from(decodeURIComponent("Y3VzdG9tZXJfbG9nby%2FDkXlrYWEucG5n"),"base64").toString() = customer_logo/Ñykaa.png + // Exactly what we want + expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-image,ie-Y3VzdG9tZXJfbG9nby9OzIN5a2FhLnBuZw%3D%3D,l-end/medium_cafe_B1iTdD0C.jpg`, + `https://ik.imagekit.io/demo/tr:l-image,ie-Y3VzdG9tZXJfbG9nby%2FDkXlrYWEucG5n,l-end/medium_cafe_B1iTdD0C.jpg`, ); }); From 788885c3cc5e8834105ec2b0b8ed28ac747b0b1a Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 16:39:47 +0530 Subject: [PATCH 12/73] chore: lint and format fix --- tests/url-generation/basic.test.ts | 248 +++++--------------- tests/url-generation/overlay.test.ts | 327 ++++++++++++++------------- 2 files changed, 230 insertions(+), 345 deletions(-) diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts index c812ad47..3189a48a 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/url-generation/basic.test.ts @@ -82,9 +82,7 @@ describe('URL generation', function () { }, ], }); - expect(url).toBe( - 'https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90', - ); + expect(url).toBe('https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90'); }); it('should generate the URL without sdk version', function () { @@ -100,9 +98,7 @@ describe('URL generation', function () { transformationPosition: 'path', }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400/test_path.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400/test_path.jpg`); }); it('should generate the correct URL with a valid src and transformation', function () { @@ -119,9 +115,7 @@ describe('URL generation', function () { }); // Now transformed URL goes into query since transformationPosition is "query". - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`); }); it('should generate the correct URL when the provided path contains multiple leading slashes', function () { @@ -137,9 +131,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`); }); it('should generate the correct URL when the urlEndpoint is overridden', function () { @@ -156,9 +148,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint_alt/test_path.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint_alt/test_path.jpg?tr=h-300,w-400`); }); it('should generate the correct URL with transformationPosition as query parameter when src is provided', function () { @@ -174,9 +164,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`); }); it('should generate the correct URL with a valid src parameter and transformation', function () { @@ -192,9 +180,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`); }); it('should generate the correct URL with transformationPosition as query parameter when src is provided', function () { @@ -210,9 +196,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path_alt.jpg?tr=h-300,w-400`); }); it('should merge query parameters correctly in the generated URL', function () { @@ -250,9 +234,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rt-90`); }); it('should generate the correct URL with chained transformations including a new undocumented transformation parameter', function () { @@ -271,9 +253,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rndm_trnsf-abcd`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400:rndm_trnsf-abcd`); }); it('should generate the correct URL when overlay image transformation is provided', function () { @@ -328,9 +308,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,b-20_FF0000`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,b-20_FF0000`); }); it('should generate the correct URL when transformation has empty key and value', function () { @@ -360,9 +338,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=undefined-transform-true`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=undefined-transform-true`); }); it('should generate the correct URL when transformation key has an empty value', function () { @@ -377,9 +353,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=di-`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=di-`); }); it("should generate the correct URL when transformation key has '-' as its value", function () { @@ -394,9 +368,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=e-contrast`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=e-contrast`); }); it('should skip transformation parameters that are undefined or null', function () { @@ -413,9 +385,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`); }); it('should skip transformation parameters that are false', function () { @@ -431,9 +401,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg`); }); it('should include only the key when transformation value is an empty string', function () { @@ -449,9 +417,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,e-shadow`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,e-shadow`); }); it('should include both key and value when transformation parameter value is provided', function () { @@ -485,9 +451,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`); }); it('should generate the correct URL when trim transformation is set to true as a string', function () { @@ -503,9 +467,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=di-test_path.jpg,t-true`); }); it('should generate the correct URL for AI background removal when set to true', function () { @@ -520,9 +482,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`); }); it("should generate the correct URL for AI background removal when 'true' is provided as a string", function () { @@ -537,9 +497,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-bgremove`); }); it('should not apply AI background removal when value is not true', function () { @@ -569,9 +527,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`); }); it("should generate the correct URL for external AI background removal when 'true' is provided as a string", function () { @@ -586,9 +542,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-removedotbg`); }); it('should not apply external AI background removal when value is not true', function () { @@ -635,9 +589,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`); }); it('should generate the correct URL when gradient transformation is set to true', function () { @@ -652,9 +604,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-gradient`); }); it('should generate the correct URL when AI drop shadow transformation is set to true', function () { @@ -669,9 +619,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`); }); it('should generate the correct URL when AI drop shadow transformation is provided as an empty string', function () { @@ -686,9 +634,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow`); }); it('should generate the correct URL when AI drop shadow transformation is provided with a specific string value', function () { @@ -703,9 +649,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow-az-45`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-dropshadow-az-45`); }); it('should generate the correct URL when shadow transformation is set to true', function () { @@ -720,9 +664,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`); }); it('should generate the correct URL when shadow transformation is provided as an empty string', function () { @@ -737,9 +679,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-shadow`); }); it('should generate the correct URL when shadow transformation is provided with a specific string value', function () { @@ -771,9 +711,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`); }); it('should generate the correct URL when sharpen transformation is provided as an empty string', function () { @@ -788,9 +726,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen`); }); it('should generate the correct URL when sharpen transformation is provided with a number value', function () { @@ -805,9 +741,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen-10`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-sharpen-10`); }); it('should generate the correct URL when unsharpMask transformation is set to true', function () { @@ -822,9 +756,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`); }); it('should generate the correct URL when unsharpMask transformation is provided as an empty string', function () { @@ -839,9 +771,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm`); }); it('should generate the correct URL when unsharpMask transformation is provided with a string value', function () { @@ -856,9 +786,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm-2-2-0.8-0.024`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=e-usm-2-2-0.8-0.024`); }); it('should generate the correct URL for trim transformation when set to true (boolean)', function () { @@ -873,9 +801,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`); }); it('should generate the correct URL for trim transformation when provided as an empty string', function () { @@ -890,9 +816,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-true`); }); it('should generate the correct URL for trim transformation when provided with a number value', function () { @@ -907,9 +831,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-5`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=t-5`); }); // Width parameter tests @@ -925,9 +847,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`); }); it('should generate the correct URL for width transformation when provided with a string value', function () { @@ -942,9 +862,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-400`); }); it('should generate the correct URL for width transformation when provided with an arithmetic expression', function () { @@ -959,9 +877,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-iw_div_2`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=w-iw_div_2`); }); // Height parameter tests @@ -977,9 +893,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`); }); it('should generate the correct URL for height transformation when provided with a string value', function () { @@ -994,9 +908,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-300`); }); it('should generate the correct URL for height transformation when provided with an arithmetic expression', function () { @@ -1011,9 +923,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-ih_mul_0.5`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=h-ih_mul_0.5`); }); // AspectRatio parameter tests @@ -1029,9 +939,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4:3`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4:3`); }); it('should generate the correct URL for aspectRatio transformation when provided with an alternate underscore format', function () { @@ -1046,9 +954,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4_3`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-4_3`); }); it('should generate the correct URL for aspectRatio transformation when provided with an arithmetic expression', function () { @@ -1063,9 +969,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-iar_div_2`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=ar-iar_div_2`); }); // Background parameter tests @@ -1081,9 +985,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-FF0000`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-FF0000`); }); it('should generate the correct URL for background transformation when provided with the blurred option', function () { @@ -1098,9 +1000,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-blurred`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-blurred`); }); it('should generate the correct URL for background transformation when provided with the genfill option', function () { @@ -1115,9 +1015,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-genfill`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=bg-genfill`); }); // Crop parameter tests @@ -1133,9 +1031,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-force`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-force`); }); it('should generate the correct URL for crop transformation when provided with at_max value', function () { @@ -1150,9 +1046,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-at_max`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=c-at_max`); }); // CropMode parameter tests @@ -1168,9 +1062,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-pad_resize`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-pad_resize`); }); it('should generate the correct URL for cropMode transformation when provided with extract value', function () { @@ -1185,9 +1077,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-extract`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=cm-extract`); }); // Focus parameter tests @@ -1203,9 +1093,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-center`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-center`); }); it('should generate the correct URL for focus transformation when face detection is specified', function () { @@ -1220,9 +1108,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-face`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=fo-face`); }); // Quality parameter test @@ -1238,9 +1124,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=q-80`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=q-80`); }); // Coordinate parameters tests @@ -1256,9 +1140,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=x-10`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=x-10`); }); it('should generate the correct URL for y coordinate transformation when provided with a number value', function () { @@ -1273,9 +1155,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=y-20`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=y-20`); }); it('should generate the correct URL for xCenter transformation when provided with a number value', function () { @@ -1290,9 +1170,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=xc-30`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=xc-30`); }); it('should generate the correct URL for yCenter transformation when provided with a number value', function () { @@ -1307,9 +1185,7 @@ describe('URL generation', function () { ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=yc-40`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path1.jpg?tr=yc-40`); }); it('Including deprecated properties', function () { diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts index f43ca11d..bf2c485c 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/url-generation/overlay.test.ts @@ -118,7 +118,9 @@ describe('Overlay Transformation Test Cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent('Minimal Text')},l-end/base-image.jpg`, + `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-${encodeURIComponent( + 'Minimal Text', + )},l-end/base-image.jpg`, ); }); @@ -136,9 +138,7 @@ describe('Overlay Transformation Test Cases', function () { }, ], }); - expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/tr:l-image,i-logo.png,l-end/base-image.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/tr:l-image,i-logo.png,l-end/base-image.jpg`); }); it('Video overlay generates correct URL with input play-pause-loop.mp4', function () { @@ -204,144 +204,153 @@ describe('Overlay Transformation Test Cases', function () { urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', src: '/base-image.jpg', transformation: [ - { - // Text overlay - overlay: { - type: "text", - text: "Every thing", - position: { - x: "10", - y: "20", - focus: "center" - }, - timing: { - start: 5, - duration: "10", - end: 15 - }, - transformation: [{ - width: "bw_mul_0.5", - fontSize: 20, - fontFamily: "Arial", - fontColor: "0000ff", - innerAlignment: "left", - padding: 5, - alpha: 7, - typography: "b", - background: "red", - radius: 10, - rotation: "N45", - flip: "h", - lineHeight: 20 - }] - } - }, - { - // Image overlay - overlay: { - type: "image", - input: "logo.png", - position: { - x: "10", - y: "20", - focus: "center" - }, - timing: { - start: 5, - duration: "10", - end: 15 - }, - transformation: [ - { - width: "bw_mul_0.5", - height: "bh_mul_0.5", - rotation: "N45", - flip: "h", - overlay: { - type: "text", - text: "Nested text overlay", - } - } - ] - } - }, - { - // Video overlay. Just for URL generation testing, you can't actually overlay a video on an image. - overlay: { - type: "video", - input: "play-pause-loop.mp4", - position: { - x: "10", - y: "20", - focus: "center" - }, - timing: { - start: 5, - duration: "10", - end: 15 - }, - transformation: [{ - width: "bw_mul_0.5", - height: "bh_mul_0.5", - rotation: "N45", - flip: "h", - }] - } - }, - { - // Subtitle overlay. Just for URL generation testing, you can't actually overlay a subtitle on an image. - overlay: { - type: "subtitle", - input: "subtitle.srt", - position: { - x: "10", - y: "20", - focus: "center" - }, - timing: { - start: 5, - duration: "10", - end: 15 - }, - transformation: [{ - background: "red", - color: "0000ff", - fontFamily: "Arial", - fontOutline: "2_A1CCDD50", - fontShadow: "A1CCDD_3" - }] - } + { + // Text overlay + overlay: { + type: 'text', + text: 'Every thing', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + fontSize: 20, + fontFamily: 'Arial', + fontColor: '0000ff', + innerAlignment: 'left', + padding: 5, + alpha: 7, + typography: 'b', + background: 'red', + radius: 10, + rotation: 'N45', + flip: 'h', + lineHeight: 20, + }, + ], + }, + }, + { + // Image overlay + overlay: { + type: 'image', + input: 'logo.png', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + height: 'bh_mul_0.5', + rotation: 'N45', + flip: 'h', + overlay: { + type: 'text', + text: 'Nested text overlay', }, - { - // Solid color overlay - overlay: { - type: "solidColor", - color: "FF0000", - position: { - x: "10", - y: "20", - focus: "center" - }, - timing: { - start: 5, - duration: "10", - end: 15 - }, - transformation: [{ - width: "bw_mul_0.5", - height: "bh_mul_0.5", - alpha: 0.5, - background: "red", - gradient: true, - radius: "max" - }] - } - } + }, + ], + }, + }, + { + // Video overlay. Just for URL generation testing, you can't actually overlay a video on an image. + overlay: { + type: 'video', + input: 'play-pause-loop.mp4', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + height: 'bh_mul_0.5', + rotation: 'N45', + flip: 'h', + }, + ], + }, + }, + { + // Subtitle overlay. Just for URL generation testing, you can't actually overlay a subtitle on an image. + overlay: { + type: 'subtitle', + input: 'subtitle.srt', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + background: 'red', + color: '0000ff', + fontFamily: 'Arial', + fontOutline: '2_A1CCDD50', + fontShadow: 'A1CCDD_3', + }, + ], + }, + }, + { + // Solid color overlay + overlay: { + type: 'solidColor', + color: 'FF0000', + position: { + x: '10', + y: '20', + focus: 'center', + }, + timing: { + start: 5, + duration: '10', + end: 15, + }, + transformation: [ + { + width: 'bw_mul_0.5', + height: 'bh_mul_0.5', + alpha: 0.5, + background: 'red', + gradient: true, + radius: 'max', + }, + ], + }, + }, ], }); - expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Every%20thing,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-Nested%20text%20overlay,l-end,l-end:l-video,i-play-pause-loop.mp4,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end:l-subtitle,i-subtitle.srt,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,bg-red,color-0000ff,ff-Arial,fol-2_A1CCDD50,fsh-A1CCDD_3,l-end:l-image,i-ik_canvas,bg-FF0000,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,al-0.5,bg-red,e-gradient,r-max,l-end/base-image.jpg`); - + expect(url).toBe( + `https://ik.imagekit.io/test_url_endpoint/tr:l-text,i-Every%20thing,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,fs-20,ff-Arial,co-0000ff,ia-left,pa-5,al-7,tg-b,bg-red,r-10,rt-N45,fl-h,lh-20,l-end:l-image,i-logo.png,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-text,i-Nested%20text%20overlay,l-end,l-end:l-video,i-play-pause-loop.mp4,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,rt-N45,fl-h,l-end:l-subtitle,i-subtitle.srt,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,bg-red,color-0000ff,ff-Arial,fol-2_A1CCDD50,fsh-A1CCDD_3,l-end:l-image,i-ik_canvas,bg-FF0000,lx-10,ly-20,lfo-center,lso-5,leo-15,ldu-10,w-bw_mul_0.5,h-bh_mul_0.5,al-0.5,bg-red,e-gradient,r-max,l-end/base-image.jpg`, + ); }); }); @@ -383,7 +392,7 @@ describe('Overlay encoding test cases', function () { // Buffer.from(decodeURIComponent("Y3VzdG9tZXJfbG9nby%2FDkXlrYWEucG5n"),"base64").toString() = customer_logo/Ñykaa.png // Exactly what we want - + expect(url).toBe( `https://ik.imagekit.io/demo/tr:l-image,ie-Y3VzdG9tZXJfbG9nby%2FDkXlrYWEucG5n,l-end/medium_cafe_B1iTdD0C.jpg`, ); @@ -403,9 +412,7 @@ describe('Overlay encoding test cases', function () { }, ], }); - expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-text,i-Manu,l-end/medium_cafe_B1iTdD0C.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/demo/tr:l-text,i-Manu,l-end/medium_cafe_B1iTdD0C.jpg`); }); it('Simple text overlay with spaces and other safe characters, should use i instead of ie', function () { @@ -423,7 +430,9 @@ describe('Overlay encoding test cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-text,i-${encodeURIComponent('alnum123-._ ')},l-end/medium_cafe_B1iTdD0C.jpg`, + `https://ik.imagekit.io/demo/tr:l-text,i-${encodeURIComponent( + 'alnum123-._ ', + )},l-end/medium_cafe_B1iTdD0C.jpg`, ); }); @@ -461,9 +470,7 @@ describe('Overlay encoding test cases', function () { }, ], }); - expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-text,i-HelloWorld,l-end/sample.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/demo/tr:l-text,i-HelloWorld,l-end/sample.jpg`); }); it('Text overlay with explicit base64 encoding', function () { @@ -482,7 +489,9 @@ describe('Overlay encoding test cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-text,ie-${encodeURIComponent(safeBtoa('HelloWorld'))},l-end/sample.jpg`, + `https://ik.imagekit.io/demo/tr:l-text,ie-${encodeURIComponent( + safeBtoa('HelloWorld'), + )},l-end/sample.jpg`, ); }); @@ -501,9 +510,7 @@ describe('Overlay encoding test cases', function () { }, ], }); - expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-image,i-customer@@logo.png,l-end/sample.jpg`, - ); + expect(url).toBe(`https://ik.imagekit.io/demo/tr:l-image,i-customer@@logo.png,l-end/sample.jpg`); }); it('Image overlay with explicit base64 encoding', function () { @@ -522,7 +529,9 @@ describe('Overlay encoding test cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-image,ie-${encodeURIComponent(safeBtoa('customer/logo.png'))},l-end/sample.jpg`, + `https://ik.imagekit.io/demo/tr:l-image,ie-${encodeURIComponent( + safeBtoa('customer/logo.png'), + )},l-end/sample.jpg`, ); }); @@ -542,7 +551,9 @@ describe('Overlay encoding test cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-video,ie-${encodeURIComponent(safeBtoa('path/to/video.mp4'))},l-end/sample.mp4`, + `https://ik.imagekit.io/demo/tr:l-video,ie-${encodeURIComponent( + safeBtoa('path/to/video.mp4'), + )},l-end/sample.mp4`, ); }); @@ -561,9 +572,7 @@ describe('Overlay encoding test cases', function () { }, ], }); - expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-subtitle,i-sub.srt,l-end/sample.mp4`, - ); + expect(url).toBe(`https://ik.imagekit.io/demo/tr:l-subtitle,i-sub.srt,l-end/sample.mp4`); }); it('Subtitle overlay with explicit base64 encoding', function () { @@ -582,7 +591,9 @@ describe('Overlay encoding test cases', function () { ], }); expect(url).toBe( - `https://ik.imagekit.io/demo/tr:l-subtitle,ie-${encodeURIComponent(safeBtoa('sub.srt'))},l-end/sample.mp4`, + `https://ik.imagekit.io/demo/tr:l-subtitle,ie-${encodeURIComponent( + safeBtoa('sub.srt'), + )},l-end/sample.mp4`, ); }); @@ -600,8 +611,6 @@ describe('Overlay encoding test cases', function () { ], transformationPosition: 'query', }); - expect(url).toBe( - `https://ik.imagekit.io/demo/sample.jpg?tr=l-text,i-Minimal%20Text,l-end`, - ); + expect(url).toBe(`https://ik.imagekit.io/demo/sample.jpg?tr=l-text,i-Minimal%20Text,l-end`); }); }); From cc1a4c0d915a9dfc6b1156f578fb1e713f965c2e Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 16:40:48 +0530 Subject: [PATCH 13/73] refactor(helper): remove console error logging in Helper class --- src/resources/helper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/resources/helper.ts b/src/resources/helper.ts index 3539e3e5..a3a85dea 100644 --- a/src/resources/helper.ts +++ b/src/resources/helper.ts @@ -289,7 +289,6 @@ export class Helper extends APIResource { isSrcParameterUsedForURL = true; } } catch (e) { - console.error(e); return ''; } From a18331d25a731109106a8e7c5c63a884e851d854 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 31 Aug 2025 17:00:35 +0530 Subject: [PATCH 14/73] refactor(tests): update URL generation test to include new aiEdit transformation parameter --- tests/url-generation/basic.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts index 3189a48a..08792ec0 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/url-generation/basic.test.ts @@ -1284,6 +1284,7 @@ describe('URL generation', function () { aiVariation: true, aiDropShadow: true, aiChangeBackground: 'prompt-car', + aiEdit: 'prompt-make it vintage', aiRemoveBackground: true, contrastStretch: true, shadow: 'bl-15_st-40_x-10_y-N5', @@ -1298,7 +1299,7 @@ describe('URL generation', function () { }); expect(url).toBe( - `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,x-10,y-20,xc-30,yc-40,fl-h,o-0.8,z-2,vc-h264,ac-aac,so-5,eo-15,du-10,sr-1440_1080,e-grayscale,e-upscale,e-retouch,e-genvar,e-dropshadow,e-changebg-prompt-car,e-bgremove,e-contrast,e-shadow-bl-15_st-40_x-10_y-N5,e-sharpen-10,e-usm-2-2-0.8-0.024,e-gradient-from-red_to-white,orig-true,pg-2_4,h-200,w-300,l-image,i-logo.png,l-end`, + `https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,x-10,y-20,xc-30,yc-40,fl-h,o-0.8,z-2,vc-h264,ac-aac,so-5,eo-15,du-10,sr-1440_1080,e-grayscale,e-upscale,e-retouch,e-genvar,e-dropshadow,e-changebg-prompt-car,e-edit-prompt-make it vintage,e-bgremove,e-contrast,e-shadow-bl-15_st-40_x-10_y-N5,e-sharpen-10,e-usm-2-2-0.8-0.024,e-gradient-from-red_to-white,orig-true,pg-2_4,h-200,w-300,l-image,i-logo.png,l-end`, ); }); }); From 2e7211e34f56a45e909db054a5dc739dc824d6e4 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 10:54:38 +0530 Subject: [PATCH 15/73] refactor(tests): remove unused imports from URL generation test files --- tests/url-generation/basic.test.ts | 1 - tests/url-generation/buildTransformationString.test.ts | 1 - tests/url-generation/overlay.test.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts index 08792ec0..d464158e 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/url-generation/basic.test.ts @@ -1,4 +1,3 @@ -import { expect } from '@jest/globals'; import ImageKit from '@imagekit/nodejs'; import type { SrcOptions } from '../../src/resources/shared'; diff --git a/tests/url-generation/buildTransformationString.test.ts b/tests/url-generation/buildTransformationString.test.ts index bf50a5e3..44310fcd 100644 --- a/tests/url-generation/buildTransformationString.test.ts +++ b/tests/url-generation/buildTransformationString.test.ts @@ -1,4 +1,3 @@ -import { expect } from '@jest/globals'; import ImageKit from '@imagekit/nodejs'; import type { Transformation } from '../../src/resources/shared'; diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts index bf2c485c..54a6ec8e 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/url-generation/overlay.test.ts @@ -1,4 +1,3 @@ -import { expect } from '@jest/globals'; import ImageKit from '@imagekit/nodejs'; import { safeBtoa } from '../../src/lib/transformation-utils'; From 55d2dd18b0c717a5ede4fea09523098d806e87af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:26:10 +0000 Subject: [PATCH 16/73] feat(api): add signed URL options with expiration settings to enhance security features --- .stats.yml | 4 ++-- src/resources/shared.ts | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 66a8df1f..cb2a3979 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-a49f0e337789b1a4368194ed004ac4c1b0c0cd2ce4344e14546422632242d897.yml -openapi_spec_hash: a7f3999c6227aac108cd80253ffc7730 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d857341f30517b11df568dd6c5a0e9dea3a854930f7f6583718114d311f2d5ee.yml +openapi_spec_hash: db94bfd556220d6244a1b2bbae9d7d00 config_hash: 249ee22f294858ab0971b8379f7cb519 diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 5d038e0e..f50d7390 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -185,6 +185,20 @@ export interface SrcOptions { */ urlEndpoint: string; + /** + * When you want the signed URL to expire, specified in seconds. If `expiresIn` is + * anything above 0, the URL will always be signed even if `signed` is set to + * false. If not specified and `signed` is `true`, the signed URL will not expire + * (valid indefinitely). + * + * Example: Setting `expiresIn: 3600` will make the URL expire 1 hour from + * generation time. After the expiry time, the signed URL will no longer be valid + * and ImageKit will return a 401 Unauthorized status code. + * + * [Learn more](https://imagekit.io/docs/media-delivery-basic-security#how-to-generate-signed-urls). + */ + expiresIn?: number; + /** * These are additional query parameters that you want to add to the final URL. * They can be any query parameters and not necessarily related to ImageKit. This @@ -192,6 +206,15 @@ export interface SrcOptions { */ queryParameters?: { [key: string]: string }; + /** + * Whether to sign the URL or not. Set this to `true` if you want to generate a + * signed URL. If `signed` is `true` and `expiresIn` is not specified, the signed + * URL will not expire (valid indefinitely). Note: If `expiresIn` is set to any + * value above 0, the URL will always be signed regardless of this setting. + * [Learn more](https://imagekit.io/docs/media-delivery-basic-security#how-to-generate-signed-urls). + */ + signed?: boolean; + /** * An array of objects specifying the transformations to be applied in the URL. If * more than one transformation is specified, they are applied in the order they From b1594d8e0e416811bb7a87e3d14492725dc1b2d4 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 13:27:56 +0530 Subject: [PATCH 17/73] feat: add url signing and test cases --- src/lib/crypto-utils.ts | 37 +++++ src/lib/transformation-utils.ts | 4 +- src/resources/helper.ts | 230 ++++++++++++++++++--------- tests/url-generation/overlay.test.ts | 24 +++ tests/url-generation/signing.test.ts | 135 ++++++++++++++++ 5 files changed, 351 insertions(+), 79 deletions(-) create mode 100644 src/lib/crypto-utils.ts create mode 100644 tests/url-generation/signing.test.ts diff --git a/src/lib/crypto-utils.ts b/src/lib/crypto-utils.ts new file mode 100644 index 00000000..174b4b82 --- /dev/null +++ b/src/lib/crypto-utils.ts @@ -0,0 +1,37 @@ +/** + * Simple synchronous crypto utilities for ImageKit SDK + * + * This module provides HMAC-SHA1 functionality using Node.js crypto module. + * URL signing is only supported in Node.js runtime. + */ + +import { ImageKitError } from '../core/error'; + +/** + * Creates an HMAC-SHA1 hash using Node.js crypto module + * + * @param key - The secret key for HMAC generation + * @param data - The data to be signed + * @returns Hex-encoded HMAC-SHA1 hash + * @throws ImageKitError if crypto module is not available or operation fails + */ +export function createHmacSha1(key: string, data: string): string { + let crypto: any; + + try { + crypto = require('crypto'); + } catch (err) { + throw new ImageKitError( + 'URL signing requires Node.js crypto module which is not available in this runtime. ' + + 'Please use Node.js environment for URL signing functionality.', + ); + } + + try { + return crypto.createHmac('sha1', key).update(data, 'utf8').digest('hex'); + } catch (error) { + throw new ImageKitError( + `Failed to generate HMAC-SHA1 signature: ${error instanceof Error ? error.message : 'Unknown error'}`, + ); + } +} diff --git a/src/lib/transformation-utils.ts b/src/lib/transformation-utils.ts index e4db32cc..17fdf742 100644 --- a/src/lib/transformation-utils.ts +++ b/src/lib/transformation-utils.ts @@ -54,6 +54,7 @@ export const supportedTransforms: { [key: string]: string } = { aiChangeBackground: 'e-changebg', aiRemoveBackground: 'e-bgremove', aiRemoveBackgroundExternal: 'e-removedotbg', + aiEdit: 'e-edit', contrastStretch: 'e-contrast', shadow: 'e-shadow', sharpen: 'e-sharpen', @@ -86,9 +87,6 @@ export const supportedTransforms: { [key: string]: string } = { // Raw pass-through raw: 'raw', - - // Additional missing mappings from JS SDK - aiEdit: 'e-edit', }; export default { diff --git a/src/resources/helper.ts b/src/resources/helper.ts index a3a85dea..6cd21211 100644 --- a/src/resources/helper.ts +++ b/src/resources/helper.ts @@ -13,11 +13,127 @@ import type { SolidColorOverlay, } from './shared'; import transformationUtils, { safeBtoa } from '../lib/transformation-utils'; +import { createHmacSha1 } from '../lib/crypto-utils'; const TRANSFORMATION_PARAMETER = 'tr'; +const SIGNATURE_PARAMETER = 'ik-s'; +const TIMESTAMP_PARAMETER = 'ik-t'; +const DEFAULT_TIMESTAMP = 9999999999; const SIMPLE_OVERLAY_PATH_REGEX = new RegExp('^[a-zA-Z0-9-._/ ]*$'); const SIMPLE_OVERLAY_TEXT_REGEX = new RegExp('^[a-zA-Z0-9-._ ]*$'); +export class Helper extends APIResource { + constructor(client: ImageKit) { + super(client); + } + + /** + * Builds a source URL with the given options. + * + * @param opts - The options for building the source URL. + * @returns The constructed source URL. + */ + buildSrc(opts: SrcOptions): string { + opts.urlEndpoint = opts.urlEndpoint || ''; + opts.src = opts.src || ''; + opts.transformationPosition = opts.transformationPosition || 'query'; + + if (!opts.src) { + return ''; + } + + const isAbsoluteURL = opts.src.startsWith('http://') || opts.src.startsWith('https://'); + + var urlObj, isSrcParameterUsedForURL, urlEndpointPattern; + + try { + if (!isAbsoluteURL) { + urlEndpointPattern = new URL(opts.urlEndpoint).pathname; + urlObj = new URL(pathJoin([opts.urlEndpoint.replace(urlEndpointPattern, ''), opts.src])); + } else { + urlObj = new URL(opts.src!); + isSrcParameterUsedForURL = true; + } + } catch (e) { + return ''; + } + + for (var i in opts.queryParameters) { + urlObj.searchParams.append(i, String(opts.queryParameters[i])); + } + + var transformationString = this.buildTransformationString(opts.transformation); + + if (transformationString && transformationString.length) { + if (!transformationUtils.addAsQueryParameter(opts) && !isSrcParameterUsedForURL) { + urlObj.pathname = pathJoin([ + TRANSFORMATION_PARAMETER + transformationUtils.getChainTransformDelimiter() + transformationString, + urlObj.pathname, + ]); + } + } + + if (urlEndpointPattern) { + urlObj.pathname = pathJoin([urlEndpointPattern, urlObj.pathname]); + } else { + urlObj.pathname = pathJoin([urlObj.pathname]); + } + + // First, build the complete URL with transformations + let finalUrl = urlObj.href; + + // Add transformation parameter manually to avoid URL encoding + // URLSearchParams.set() would encode commas and colons in transformation string, + // It would work correctly but not very readable e.g., "w-300,h-400" is better than "w-300%2Ch-400" + if (transformationString && transformationString.length) { + if (transformationUtils.addAsQueryParameter(opts) || isSrcParameterUsedForURL) { + const separator = urlObj.searchParams.toString() ? '&' : '?'; + finalUrl = `${finalUrl}${separator}${TRANSFORMATION_PARAMETER}=${transformationString}`; + } + } + + // Then sign the URL if needed + if (opts.signed === true || (opts.expiresIn && opts.expiresIn > 0)) { + const expiryTimestamp = getSignatureTimestamp(opts.expiresIn); + + const urlSignature = getSignature({ + privateKey: this._client.privateAPIKey, + url: finalUrl, + urlEndpoint: opts.urlEndpoint, + expiryTimestamp, + }); + + // Add signature parameters to the final URL + // Use URL object to properly determine if we need ? or & separator + const finalUrlObj = new URL(finalUrl); + const hasExistingParams = finalUrlObj.searchParams.toString().length > 0; + const separator = hasExistingParams ? '&' : '?'; + let signedUrl = finalUrl; + + if (expiryTimestamp && expiryTimestamp !== DEFAULT_TIMESTAMP) { + signedUrl += `${separator}${TIMESTAMP_PARAMETER}=${expiryTimestamp}`; + signedUrl += `&${SIGNATURE_PARAMETER}=${urlSignature}`; + } else { + signedUrl += `${separator}${SIGNATURE_PARAMETER}=${urlSignature}`; + } + + return signedUrl; + } + + return finalUrl; + } + + /** + * Builds a transformation string from the given transformations. + * + * @param transformation - The transformations to apply. + * @returns The constructed transformation string. + */ + buildTransformationString(transformation: Transformation[] | undefined): string { + return buildTransformationString(transformation); + } +} + function removeTrailingSlash(str: string): string { if (typeof str == 'string' && str[str.length - 1] == '/') { str = str.substring(0, str.length - 1); @@ -231,7 +347,7 @@ function buildTransformationString(transformation: Transformation[] | undefined) } else if (key === 'raw') { parsedTransformStep.push(currentTransform[key] as string); } else { - if (transformKey === 'di') { + if (transformKey === 'di' || transformKey === 'ff') { value = removeTrailingSlash(removeLeadingSlash((value as string) || '')); value = value.replace(/\//g, '@@'); } @@ -256,84 +372,46 @@ function buildTransformationString(transformation: Transformation[] | undefined) return parsedTransforms.join(transformationUtils.getChainTransformDelimiter()); } -export class Helper extends APIResource { - constructor(client: ImageKit) { - super(client); - } +/** + * Calculates the expiry timestamp for URL signing + * + * @param seconds - Number of seconds from now when the URL should expire + * @returns Unix timestamp for expiry, or DEFAULT_TIMESTAMP if invalid/not provided + */ +function getSignatureTimestamp(seconds: number | undefined): number { + if (!seconds || seconds <= 0) return DEFAULT_TIMESTAMP; - /** - * Builds a source URL with the given options. - * - * @param opts - The options for building the source URL. - * @returns The constructed source URL. - */ - buildSrc(opts: SrcOptions): string { - opts.urlEndpoint = opts.urlEndpoint || ''; - opts.src = opts.src || ''; - opts.transformationPosition = opts.transformationPosition || 'query'; + const sec = parseInt(String(seconds), 10); + if (!sec || isNaN(sec)) return DEFAULT_TIMESTAMP; - if (!opts.src) { - return ''; - } - - const isAbsoluteURL = opts.src.startsWith('http://') || opts.src.startsWith('https://'); - - var urlObj, isSrcParameterUsedForURL, urlEndpointPattern; - - try { - if (!isAbsoluteURL) { - urlEndpointPattern = new URL(opts.urlEndpoint).pathname; - urlObj = new URL(pathJoin([opts.urlEndpoint.replace(urlEndpointPattern, ''), opts.src])); - } else { - urlObj = new URL(opts.src!); - isSrcParameterUsedForURL = true; - } - } catch (e) { - return ''; - } - - for (var i in opts.queryParameters) { - urlObj.searchParams.append(i, String(opts.queryParameters[i])); - } - - var transformationString = this.buildTransformationString(opts.transformation); - - if (transformationString && transformationString.length) { - if (!transformationUtils.addAsQueryParameter(opts) && !isSrcParameterUsedForURL) { - urlObj.pathname = pathJoin([ - TRANSFORMATION_PARAMETER + transformationUtils.getChainTransformDelimiter() + transformationString, - urlObj.pathname, - ]); - } - } - - if (urlEndpointPattern) { - urlObj.pathname = pathJoin([urlEndpointPattern, urlObj.pathname]); - } else { - urlObj.pathname = pathJoin([urlObj.pathname]); - } + const currentTimestamp = Math.floor(new Date().getTime() / 1000); + return currentTimestamp + sec; +} - if (transformationString && transformationString.length) { - if (transformationUtils.addAsQueryParameter(opts) || isSrcParameterUsedForURL) { - if (urlObj.searchParams.toString() !== '') { - // In 12 node.js .size was not there. So, we need to check if it is an object or not. - return `${urlObj.href}&${TRANSFORMATION_PARAMETER}=${transformationString}`; - } else { - return `${urlObj.href}?${TRANSFORMATION_PARAMETER}=${transformationString}`; - } - } - } +/** + * Generates an HMAC-SHA1 signature for URL signing + * + * @param opts - Options containing private key, URL, endpoint, and expiry timestamp + * @returns Hex-encoded signature, or empty string if required params missing + */ +function getSignature(opts: { + privateKey: string; + url: string; + urlEndpoint: string; + expiryTimestamp: number; +}): string { + if (!opts.privateKey || !opts.url || !opts.urlEndpoint) return ''; + + // Create the string to sign: relative path + expiry timestamp + const stringToSign = + opts.url.replace(addTrailingSlash(opts.urlEndpoint), '') + String(opts.expiryTimestamp); + + return createHmacSha1(opts.privateKey, stringToSign); +} - return urlObj.href; - } - - /** - * Builds a transformation string from the given transformations. - * - * @param transformation - The transformations to apply. - * @returns The constructed transformation string. - */ - buildTransformationString(transformation: Transformation[] | undefined): string { - return buildTransformationString(transformation); +function addTrailingSlash(str: string): string { + if (typeof str === 'string' && str[str.length - 1] !== '/') { + str = str + '/'; } + return str; } diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts index 54a6ec8e..7d43bd7d 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/url-generation/overlay.test.ts @@ -414,6 +414,30 @@ describe('Overlay encoding test cases', function () { expect(url).toBe(`https://ik.imagekit.io/demo/tr:l-text,i-Manu,l-end/medium_cafe_B1iTdD0C.jpg`); }); + it('Handle slash in fontFamily in case of custom fonts', function () { + const url = client.helper.buildSrc({ + transformationPosition: 'path', + urlEndpoint: 'https://ik.imagekit.io/demo', + src: '/medium_cafe_B1iTdD0C.jpg', + transformation: [ + { + overlay: { + type: 'text', + text: 'Manu', + transformation: [ + { + fontFamily: 'nested-path/Poppins-Regular_Q15GrYWmL.ttf', + }, + ], + }, + }, + ], + }); + expect(url).toBe( + `https://ik.imagekit.io/demo/tr:l-text,i-Manu,ff-nested-path@@Poppins-Regular_Q15GrYWmL.ttf,l-end/medium_cafe_B1iTdD0C.jpg`, + ); + }); + it('Simple text overlay with spaces and other safe characters, should use i instead of ie', function () { const url = client.helper.buildSrc({ transformationPosition: 'path', diff --git a/tests/url-generation/signing.test.ts b/tests/url-generation/signing.test.ts new file mode 100644 index 00000000..ffc53bd6 --- /dev/null +++ b/tests/url-generation/signing.test.ts @@ -0,0 +1,135 @@ +import ImageKit from '@imagekit/nodejs'; + +/** + * READ ME + * Always test with real account and real private key, by uploading a private file, so that we know the URL is working as expected. + * DO NOT COMMIT ACTUAL PRIVATE KEYS OR SENSITIVE INFORMATION + * Once everything is working, just replace key with `dummy-key` and update assertions to pass test suite. + * Ideally this code would not require any upkeeping. + */ +const client = new ImageKit({ + privateAPIKey: 'dummy-key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('URL Signing', function () { + it('should generate a signed URL when signed is true without expiresIn', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/sdk-testing-files/future-search.png?ik-s=32dbbbfc5f945c0403c71b54c38e76896ef2d6b0', + ); + }); + + it('should generate a signed URL when signed is true with expiresIn', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + signed: true, + expiresIn: 3600, + }); + + // Expect ik-t exist in the URL. We don't assert signature because it will keep changing. + expect(url).toContain('ik-t'); + }); + + it('should generate a signed URL when expiresIn is above 0 and even if signed is false', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + signed: false, + expiresIn: 3600, + }); + + // Expect ik-t exist in the URL. We don't assert signature because it will keep changing. + expect(url).toContain('ik-t'); + }); + + it('Special characters', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/हिन्दी.png', + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/sdk-testing-files/%E0%A4%B9%E0%A4%BF%E0%A4%A8%E0%A5%8D%E0%A4%A6%E0%A5%80.png?ik-s=3fff2f31da1f45e007adcdbe95f88c8c330e743c', + ); + }); + + it('Text overlay with special characters', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/हिन्दी.png', + transformation: [ + { + overlay: { + type: 'text', + text: 'हिन्दी', + transformation: [ + { + fontColor: 'red', + fontSize: '32', + fontFamily: 'sdk-testing-files/Poppins-Regular_Q15GrYWmL.ttf', + }, + ], + }, + }, + ], + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/sdk-testing-files/%E0%A4%B9%E0%A4%BF%E0%A4%A8%E0%A5%8D%E0%A4%A6%E0%A5%80.png?tr=l-text,ie-4KS54KS%2F4KSo4KWN4KSm4KWA,co-red,fs-32,ff-sdk-testing-files@@Poppins-Regular_Q15GrYWmL.ttf,l-end&ik-s=ac9f24a03080102555e492185533c1ae6bd93fa7', + ); + }); + + it('should generate signed URL with query parameters', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + queryParameters: { + version: '1.0', + cache: 'false', + }, + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/sdk-testing-files/future-search.png?version=1.0&cache=false&ik-s=f2e5a1b8b6a0b03fd63789dfc6413a94acef9fd8', + ); + }); + + it('should generate signed URL with transformations and query parameters', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + transformation: [{ width: 300, height: 200 }], + queryParameters: { + version: '2.0', + }, + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/sdk-testing-files/future-search.png?version=2.0&tr=w-300,h-200&ik-s=601d97a7834b7554f4dabf0d3fc3a219ceeb6b31', + ); + }); + + it('should not sign URL when signed is false', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + signed: false, + }); + + expect(url).toBe('https://ik.imagekit.io/demo/sdk-testing-files/future-search.png'); + expect(url).not.toContain('ik-s='); + expect(url).not.toContain('ik-t='); + }); +}); From 297bb95dabb0ed878bd009e1878b418ed26bf31e Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 13:48:55 +0530 Subject: [PATCH 18/73] feat(helper): implement getAuthenticationParameters method and test cases --- src/resources/helper.ts | 45 +++++++++++++++++++++ tests/helper-authentication.test.ts | 62 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 tests/helper-authentication.test.ts diff --git a/src/resources/helper.ts b/src/resources/helper.ts index 6cd21211..4d48568d 100644 --- a/src/resources/helper.ts +++ b/src/resources/helper.ts @@ -14,6 +14,7 @@ import type { } from './shared'; import transformationUtils, { safeBtoa } from '../lib/transformation-utils'; import { createHmacSha1 } from '../lib/crypto-utils'; +import { uuid4 } from '../internal/utils/uuid'; const TRANSFORMATION_PARAMETER = 'tr'; const SIGNATURE_PARAMETER = 'ik-s'; @@ -132,8 +133,52 @@ export class Helper extends APIResource { buildTransformationString(transformation: Transformation[] | undefined): string { return buildTransformationString(transformation); } + + /** + * Generates authentication parameters for client-side file uploads using ImageKit's Upload API V1. + * + * This method creates the required authentication signature that allows secure file uploads + * directly from the browser or mobile applications without exposing your private API key. + * The generated parameters include a unique token, expiration timestamp, and HMAC signature. + * + * @param token - Custom token for the upload session. If not provided, a UUID v4 will be generated automatically. + * @param expire - Expiration time in seconds from now. If not provided, defaults to 1800 seconds (30 minutes). + * @returns Authentication parameters object containing: + * - `token`: Unique identifier for this upload session + * - `expire`: Unix timestamp when these parameters expire + * - `signature`: HMAC-SHA1 signature for authenticating the upload + * + * @throws {Error} If the private API key is not configured (should not happen in normal usage) + */ + getAuthenticationParameters(token?: string, expire?: number) { + if (!this._client.privateAPIKey) { + throw new Error('Private API key is required for authentication parameters generation'); + } + + const DEFAULT_TIME_DIFF = 60 * 30; + const defaultExpire = Math.floor(Date.now() / 1000) + DEFAULT_TIME_DIFF; + + const finalToken = token || uuid4(); + const finalExpire = expire || defaultExpire; + + return getAuthenticationParameters(finalToken, finalExpire, this._client.privateAPIKey); + } } +const getAuthenticationParameters = function (token: string, expire: number, privateKey: string) { + var authParameters = { + token: token, + expire: expire, + signature: '', + }; + + var signature = createHmacSha1(privateKey, token + expire); + + authParameters.signature = signature; + + return authParameters; +}; + function removeTrailingSlash(str: string): string { if (typeof str == 'string' && str[str.length - 1] == '/') { str = str.substring(0, str.length - 1); diff --git a/tests/helper-authentication.test.ts b/tests/helper-authentication.test.ts new file mode 100644 index 00000000..2b40e317 --- /dev/null +++ b/tests/helper-authentication.test.ts @@ -0,0 +1,62 @@ +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'private_key_test', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('Helper Authentication Parameters', function () { + it('should return correct authentication parameters with provided token and expire', function () { + const authenticationParameters = client.helper.getAuthenticationParameters('your_token', 1582269249); + + expect(authenticationParameters).toEqual({ + token: 'your_token', + expire: 1582269249, + signature: 'e71bcd6031016b060d349d212e23e85c791decdd', + }); + }); + + it('should return authentication parameters with required properties when no params provided', function () { + const authenticationParameters = client.helper.getAuthenticationParameters(); + + expect(authenticationParameters).toHaveProperty('token'); + expect(authenticationParameters).toHaveProperty('expire'); + expect(authenticationParameters).toHaveProperty('signature'); + + // Token should be a UUID (36 characters with dashes) + expect(authenticationParameters.token).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, + ); + + // Expire should be a number greater than current time + expect(typeof authenticationParameters.expire).toBe('number'); + expect(authenticationParameters.expire).toBeGreaterThan(Math.floor(Date.now() / 1000)); + + // Signature should be a hex string (40 characters for HMAC-SHA1) + expect(authenticationParameters.signature).toMatch(/^[a-f0-9]{40}$/); + }); + + it('should handle edge case with expire time 0', function () { + // When expire is 0, it's falsy, so the method uses default expire time + const authenticationParameters = client.helper.getAuthenticationParameters('test-token', 0); + + expect(authenticationParameters.token).toBe('test-token'); + // Since 0 is falsy, it should use the default expire (30 minutes from now) + const expectedExpire = Math.floor(Date.now() / 1000) + 60 * 30; + expect(authenticationParameters.expire).toBeCloseTo(expectedExpire, -1); + expect(authenticationParameters.signature).toMatch(/^[a-f0-9]{40}$/); + }); + + it('should handle empty string token', function () { + // When token is empty string, it's falsy, so the method generates a UUID + const authenticationParameters = client.helper.getAuthenticationParameters('', 1582269249); + + // Since '' is falsy, it should generate a UUID + expect(authenticationParameters.token).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, + ); + expect(authenticationParameters.expire).toBe(1582269249); + expect(authenticationParameters.signature).toMatch(/^[a-f0-9]{40}$/); + }); +}); From 7a2bc8f71d50a730fa7ebf634d2775c30d21171f Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 13:55:14 +0530 Subject: [PATCH 19/73] feat(docs): add URL generation examples and authentication parameters to README --- README.md | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/README.md b/README.md index 4bd166cd..c9f18643 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,203 @@ await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); ``` +## URL generation + +The ImageKit SDK provides a powerful `helper.buildSrc()` method for generating optimized image and video URLs with transformations. Here are examples ranging from simple URLs to complex transformations with overlays and signed URLs. + +### Basic URL generation + +Generate a simple URL without any transformations: + +```ts +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], + password: process.env['ORG_MY_PASSWORD_TOKEN'], +}); + +// Basic URL without transformations +const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/image.jpg' +}); +// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg +``` + +### URL generation with transformations + +Apply common transformations like resizing, cropping, and format conversion: + +```ts +// URL with basic transformations +const transformedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/image.jpg', + transformation: [ + { + width: 400, + height: 300, + crop: 'maintain_ratio', + quality: 80, + format: 'webp' + } + ] +}); +// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg?tr=w-400,h-300,c-maintain_ratio,q-80,f-webp +``` + +### URL generation with image overlay + +Add image overlays to your base image: + +```ts +// URL with image overlay +const imageOverlayUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 500, + height: 400, + overlay: { + type: 'image', + input: '/path/to/overlay-logo.png', + position: { + x: 10, + y: 10 + }, + transformation: [ + { + width: 100, + height: 50 + } + ] + } + } + ] +}); +// Result: URL with image overlay positioned at x:10, y:10 +``` + +### URL generation with text overlay + +Add customized text overlays: + +```ts +// URL with text overlay +const textOverlayUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 600, + height: 400, + overlay: { + type: 'text', + text: 'Sample Text Overlay', + position: { + x: 50, + y: 50, + focus: 'center' + }, + transformation: [ + { + fontSize: 40, + fontFamily: 'Arial', + fontColor: 'FFFFFF', + typography: 'b' // bold + } + ] + } + } + ] +}); +// Result: URL with bold white Arial text overlay at center position +``` + +### URL generation with multiple overlays + +Combine multiple overlays for complex compositions: + +```ts +// URL with multiple overlays (text + image) +const multipleOverlaysUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 800, + height: 600, + overlay: { + type: 'text', + text: 'Header Text', + position: { x: 20, y: 20 }, + transformation: [{ fontSize: 30, fontColor: '000000' }] + } + }, + { + overlay: { + type: 'image', + input: '/watermark.png', + position: { focus: 'bottom_right' }, + transformation: [{ width: 100, opacity: 70 }] + } + } + ] +}); +// Result: URL with text overlay at top-left and semi-transparent watermark at bottom-right +``` + +### Signed URLs for secure delivery + +Generate signed URLs that expire after a specified time for secure content delivery: + +```ts +// Generate a signed URL that expires in 1 hour (3600 seconds) +const signedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/private/secure-image.jpg', + transformation: [ + { + width: 400, + height: 300, + quality: 90 + } + ], + signed: true, + expiresIn: 3600 // URL expires in 1 hour +}); +// Result: URL with signature parameters (?ik-t=timestamp&ik-s=signature) + +// Generate a signed URL that doesn't expire +const permanentSignedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/private/secure-image.jpg', + signed: true + // No expiresIn means the URL won't expire +}); +// Result: URL with signature parameter (?ik-s=signature) +``` + +### Authentication parameters for client-side uploads + +Generate authentication parameters for secure client-side file uploads: + +```ts +// Generate authentication parameters for client-side uploads +const authParams = client.helper.getAuthenticationParameters(); +console.log(authParams); +// Result: { token: 'uuid-token', expire: timestamp, signature: 'hmac-signature' } + +// Generate with custom token and expiry +const customAuthParams = client.helper.getAuthenticationParameters('my-custom-token', 1800); +console.log(customAuthParams); +// Result: { token: 'my-custom-token', expire: 1800, signature: 'hmac-signature' } +``` + +These authentication parameters can be used in client-side upload forms to securely upload files without exposing your private API key. + ## Handling errors When the library is unable to connect to the API, From 21caa9336a890568790d5b2bb49c274ed2434c4e Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 13:55:34 +0530 Subject: [PATCH 20/73] fix(docs): add missing commas in URL generation examples for clarity --- README.md | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c9f18643..65f81afd 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ const client = new ImageKit({ // Basic URL without transformations const url = client.helper.buildSrc({ urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/image.jpg' + src: '/path/to/image.jpg', }); // Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg ``` @@ -128,9 +128,9 @@ const transformedUrl = client.helper.buildSrc({ height: 300, crop: 'maintain_ratio', quality: 80, - format: 'webp' - } - ] + format: 'webp', + }, + ], }); // Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg?tr=w-400,h-300,c-maintain_ratio,q-80,f-webp ``` @@ -153,17 +153,17 @@ const imageOverlayUrl = client.helper.buildSrc({ input: '/path/to/overlay-logo.png', position: { x: 10, - y: 10 + y: 10, }, transformation: [ { width: 100, - height: 50 - } - ] - } - } - ] + height: 50, + }, + ], + }, + }, + ], }); // Result: URL with image overlay positioned at x:10, y:10 ``` @@ -187,19 +187,19 @@ const textOverlayUrl = client.helper.buildSrc({ position: { x: 50, y: 50, - focus: 'center' + focus: 'center', }, transformation: [ { fontSize: 40, fontFamily: 'Arial', fontColor: 'FFFFFF', - typography: 'b' // bold - } - ] - } - } - ] + typography: 'b', // bold + }, + ], + }, + }, + ], }); // Result: URL with bold white Arial text overlay at center position ``` @@ -221,18 +221,18 @@ const multipleOverlaysUrl = client.helper.buildSrc({ type: 'text', text: 'Header Text', position: { x: 20, y: 20 }, - transformation: [{ fontSize: 30, fontColor: '000000' }] - } + transformation: [{ fontSize: 30, fontColor: '000000' }], + }, }, { overlay: { type: 'image', input: '/watermark.png', position: { focus: 'bottom_right' }, - transformation: [{ width: 100, opacity: 70 }] - } - } - ] + transformation: [{ width: 100, opacity: 70 }], + }, + }, + ], }); // Result: URL with text overlay at top-left and semi-transparent watermark at bottom-right ``` @@ -250,11 +250,11 @@ const signedUrl = client.helper.buildSrc({ { width: 400, height: 300, - quality: 90 - } + quality: 90, + }, ], signed: true, - expiresIn: 3600 // URL expires in 1 hour + expiresIn: 3600, // URL expires in 1 hour }); // Result: URL with signature parameters (?ik-t=timestamp&ik-s=signature) @@ -262,7 +262,7 @@ const signedUrl = client.helper.buildSrc({ const permanentSignedUrl = client.helper.buildSrc({ urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', src: '/private/secure-image.jpg', - signed: true + signed: true, // No expiresIn means the URL won't expire }); // Result: URL with signature parameter (?ik-s=signature) From dd9804078ee46a656f8423de2845482bdaca6be8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 08:31:45 +0000 Subject: [PATCH 21/73] feat(api): add new webhook events for upload transformations to enhance event tracking --- .stats.yml | 2 +- api.md | 12 +- src/client.ts | 16 +- src/resources/index.ts | 8 +- src/resources/webhooks.ts | 1421 +++++++++++++------------------------ 5 files changed, 491 insertions(+), 968 deletions(-) diff --git a/.stats.yml b/.stats.yml index cb2a3979..6bb89db0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d857341f30517b11df568dd6c5a0e9dea3a854930f7f6583718114d311f2d5ee.yml openapi_spec_hash: db94bfd556220d6244a1b2bbae9d7d00 -config_hash: 249ee22f294858ab0971b8379f7cb519 +config_hash: 4ef178e13ecfdb97211f284f13a21e83 diff --git a/api.md b/api.md index 043e01a6..991d03ea 100644 --- a/api.md +++ b/api.md @@ -208,17 +208,13 @@ Methods: Types: +- UploadPostTransformErrorEvent +- UploadPostTransformSuccessEvent +- UploadPreTransformErrorEvent +- UploadPreTransformSuccessEvent - VideoTransformationAcceptedEvent - VideoTransformationErrorEvent - VideoTransformationReadyEvent -- UploadPreTransformSuccessWebhookEvent -- UploadPreTransformErrorWebhookEvent -- UploadPostTransformSuccessWebhookEvent -- UploadPostTransformErrorWebhookEvent -- UploadPreTransformSuccessWebhookEvent -- UploadPreTransformErrorWebhookEvent -- UploadPostTransformSuccessWebhookEvent -- UploadPostTransformErrorWebhookEvent - UnsafeUnwrapWebhookEvent - UnwrapWebhookEvent diff --git a/src/client.ts b/src/client.ts index 04797821..d4ac7d3f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -29,10 +29,10 @@ import { import { UnsafeUnwrapWebhookEvent, UnwrapWebhookEvent, - UploadPostTransformErrorWebhookEvent, - UploadPostTransformSuccessWebhookEvent, - UploadPreTransformErrorWebhookEvent, - UploadPreTransformSuccessWebhookEvent, + UploadPostTransformErrorEvent, + UploadPostTransformSuccessEvent, + UploadPreTransformErrorEvent, + UploadPreTransformSuccessEvent, VideoTransformationAcceptedEvent, VideoTransformationErrorEvent, VideoTransformationReadyEvent, @@ -876,13 +876,13 @@ export declare namespace ImageKit { export { Webhooks as Webhooks, + type UploadPostTransformErrorEvent as UploadPostTransformErrorEvent, + type UploadPostTransformSuccessEvent as UploadPostTransformSuccessEvent, + type UploadPreTransformErrorEvent as UploadPreTransformErrorEvent, + type UploadPreTransformSuccessEvent as UploadPreTransformSuccessEvent, type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent as VideoTransformationErrorEvent, type VideoTransformationReadyEvent as VideoTransformationReadyEvent, - type UploadPreTransformSuccessWebhookEvent as UploadPreTransformSuccessWebhookEvent, - type UploadPreTransformErrorWebhookEvent as UploadPreTransformErrorWebhookEvent, - type UploadPostTransformSuccessWebhookEvent as UploadPostTransformSuccessWebhookEvent, - type UploadPostTransformErrorWebhookEvent as UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent as UnwrapWebhookEvent, }; diff --git a/src/resources/index.ts b/src/resources/index.ts index dea7b1c2..d9fd9d97 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -45,13 +45,13 @@ export { } from './folders/folders'; export { Webhooks, + type UploadPostTransformErrorEvent, + type UploadPostTransformSuccessEvent, + type UploadPreTransformErrorEvent, + type UploadPreTransformSuccessEvent, type VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent, type VideoTransformationReadyEvent, - type UploadPreTransformSuccessWebhookEvent, - type UploadPreTransformErrorWebhookEvent, - type UploadPostTransformSuccessWebhookEvent, - type UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent, } from './webhooks'; diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index 94e52cfb..cf6c7c4a 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -24,481 +24,228 @@ export class Webhooks extends APIResource { } /** - * Triggered when a new video transformation request is accepted for processing. - * This event confirms that ImageKit has received and queued your transformation - * request. Use this for debugging and tracking transformation lifecycle. + * Triggered when a post-transformation fails. The original file remains available, + * but the requested transformation could not be generated. */ -export interface VideoTransformationAcceptedEvent { +export interface UploadPostTransformErrorEvent { /** * Unique identifier for the event. */ id: string; /** - * Timestamp when the event was created in ISO8601 format. + * Timestamp of when the event occurred in ISO8601 format. */ created_at: string; - data: VideoTransformationAcceptedEvent.Data; + data: UploadPostTransformErrorEvent.Data; - /** - * Information about the original request that triggered the video transformation. - */ - request: VideoTransformationAcceptedEvent.Request; + request: UploadPostTransformErrorEvent.Request; - type: 'video.transformation.accepted'; + type: 'upload.post-transform.error'; } -export namespace VideoTransformationAcceptedEvent { +export namespace UploadPostTransformErrorEvent { export interface Data { /** - * Information about the source video asset being transformed. + * Unique identifier of the originally uploaded file. */ - asset: Data.Asset; + fileId: string; /** - * Base information about a video transformation request. + * Name of the file. + */ + name: string; + + /** + * Path of the file. */ + path: string; + transformation: Data.Transformation; - } - export namespace Data { /** - * Information about the source video asset being transformed. + * URL of the attempted post-transformation. */ - export interface Asset { - /** - * URL to download or access the source video file. - */ - url: string; + url: string; + } + + export namespace Data { + export interface Transformation { + error: Transformation.Error; + } + + export namespace Transformation { + export interface Error { + /** + * Reason for the post-transformation failure. + */ + reason: string; + } } + } + + export interface Request { + transformation: Request.Transformation; /** - * Base information about a video transformation request. + * Unique identifier for the originating request. */ + x_request_id: string; + } + + export namespace Request { export interface Transformation { /** - * Type of video transformation: - * - * - `video-transformation`: Standard video processing (resize, format conversion, - * etc.) - * - `gif-to-video`: Convert animated GIF to video format - * - `video-thumbnail`: Generate thumbnail image from video + * Type of the requested post-transformation. */ - type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; /** - * Configuration options for video transformations. + * Only applicable if transformation type is 'abs'. Streaming protocol used. */ - options?: Transformation.Options; - } + protocol?: 'hls' | 'dash'; - export namespace Transformation { /** - * Configuration options for video transformations. + * Value for the requested transformation type. */ - export interface Options { - /** - * Audio codec used for encoding (aac or opus). - */ - audio_codec?: 'aac' | 'opus'; + value?: string; + } + } +} - /** - * Whether to automatically rotate the video based on metadata. - */ - auto_rotate?: boolean; +/** + * Triggered when a post-transformation completes successfully. The transformed + * version of the file is now ready and can be accessed via the provided URL. Note + * that each post-transformation generates a separate webhook event. + */ +export interface UploadPostTransformSuccessEvent { + /** + * Unique identifier for the event. + */ + id: string; - /** - * Output format for the transformed video or thumbnail. - */ - format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + /** + * Timestamp of when the event occurred in ISO8601 format. + */ + created_at: string; - /** - * Quality setting for the output video. - */ - quality?: number; + data: UploadPostTransformSuccessEvent.Data; - /** - * Streaming protocol for adaptive bitrate streaming. - */ - stream_protocol?: 'HLS' | 'DASH'; + request: UploadPostTransformSuccessEvent.Request; - /** - * Array of quality representations for adaptive bitrate streaming. - */ - variants?: Array; + type: 'upload.post-transform.success'; +} - /** - * Video codec used for encoding (h264, vp9, or av1). - */ - video_codec?: 'h264' | 'vp9' | 'av1'; - } - } - } +export namespace UploadPostTransformSuccessEvent { + export interface Data { + /** + * Unique identifier of the originally uploaded file. + */ + fileId: string; - /** - * Information about the original request that triggered the video transformation. - */ - export interface Request { /** - * Full URL of the transformation request that was submitted. + * Name of the file. */ - url: string; + name: string; /** - * Unique identifier for the originating transformation request. + * URL of the generated post-transformation. */ - x_request_id: string; + url: string; + } + + export interface Request { + transformation: Request.Transformation; /** - * User-Agent header from the original request that triggered the transformation. + * Unique identifier for the originating request. */ - user_agent?: string; + x_request_id: string; + } + + export namespace Request { + export interface Transformation { + /** + * Type of the requested post-transformation. + */ + type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + + /** + * Only applicable if transformation type is 'abs'. Streaming protocol used. + */ + protocol?: 'hls' | 'dash'; + + /** + * Value for the requested transformation type. + */ + value?: string; + } } } /** - * Triggered when an error occurs during video encoding. Listen to this webhook to - * log error reasons and debug issues. Check your origin and URL endpoint settings - * if the reason is related to download failure. For other errors, contact ImageKit - * support. + * Triggered when a pre-transformation fails. The file upload may have been + * accepted, but the requested transformation could not be applied. */ -export interface VideoTransformationErrorEvent { +export interface UploadPreTransformErrorEvent { /** * Unique identifier for the event. */ id: string; /** - * Timestamp when the event was created in ISO8601 format. + * Timestamp of when the event occurred in ISO8601 format. */ created_at: string; - data: VideoTransformationErrorEvent.Data; + data: UploadPreTransformErrorEvent.Data; - /** - * Information about the original request that triggered the video transformation. - */ - request: VideoTransformationErrorEvent.Request; + request: UploadPreTransformErrorEvent.Request; - type: 'video.transformation.error'; + type: 'upload.pre-transform.error'; } -export namespace VideoTransformationErrorEvent { +export namespace UploadPreTransformErrorEvent { export interface Data { /** - * Information about the source video asset being transformed. + * Name of the file. */ - asset: Data.Asset; + name: string; + + /** + * Path of the file. + */ + path: string; transformation: Data.Transformation; } export namespace Data { - /** - * Information about the source video asset being transformed. - */ - export interface Asset { - /** - * URL to download or access the source video file. - */ - url: string; - } - export interface Transformation { - /** - * Type of video transformation: - * - * - `video-transformation`: Standard video processing (resize, format conversion, - * etc.) - * - `gif-to-video`: Convert animated GIF to video format - * - `video-thumbnail`: Generate thumbnail image from video - */ - type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; - - /** - * Details about the transformation error. - */ - error?: Transformation.Error; - - /** - * Configuration options for video transformations. - */ - options?: Transformation.Options; + error: Transformation.Error; } export namespace Transformation { - /** - * Details about the transformation error. - */ export interface Error { /** - * Specific reason for the transformation failure: - * - * - `encoding_failed`: Error during video encoding process - * - `download_failed`: Could not download source video - * - `internal_server_error`: Unexpected server error + * Reason for the pre-transformation failure. */ - reason: 'encoding_failed' | 'download_failed' | 'internal_server_error'; + reason: string; } + } + } - /** - * Configuration options for video transformations. - */ - export interface Options { - /** - * Audio codec used for encoding (aac or opus). - */ - audio_codec?: 'aac' | 'opus'; - - /** - * Whether to automatically rotate the video based on metadata. - */ - auto_rotate?: boolean; - - /** - * Output format for the transformed video or thumbnail. - */ - format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; - - /** - * Quality setting for the output video. - */ - quality?: number; - - /** - * Streaming protocol for adaptive bitrate streaming. - */ - stream_protocol?: 'HLS' | 'DASH'; - - /** - * Array of quality representations for adaptive bitrate streaming. - */ - variants?: Array; - - /** - * Video codec used for encoding (h264, vp9, or av1). - */ - video_codec?: 'h264' | 'vp9' | 'av1'; - } - } - } - - /** - * Information about the original request that triggered the video transformation. - */ - export interface Request { - /** - * Full URL of the transformation request that was submitted. - */ - url: string; - - /** - * Unique identifier for the originating transformation request. - */ - x_request_id: string; - - /** - * User-Agent header from the original request that triggered the transformation. - */ - user_agent?: string; - } -} - -/** - * Triggered when video encoding is finished and the transformed resource is ready - * to be served. This is the key event to listen for - update your database or CMS - * flags when you receive this so your application can start showing the - * transformed video to users. - */ -export interface VideoTransformationReadyEvent { - /** - * Unique identifier for the event. - */ - id: string; - - /** - * Timestamp when the event was created in ISO8601 format. - */ - created_at: string; - - data: VideoTransformationReadyEvent.Data; - - /** - * Information about the original request that triggered the video transformation. - */ - request: VideoTransformationReadyEvent.Request; - - type: 'video.transformation.ready'; - - /** - * Performance metrics for the transformation process. - */ - timings?: VideoTransformationReadyEvent.Timings; -} - -export namespace VideoTransformationReadyEvent { - export interface Data { - /** - * Information about the source video asset being transformed. - */ - asset: Data.Asset; - - transformation: Data.Transformation; - } - - export namespace Data { - /** - * Information about the source video asset being transformed. - */ - export interface Asset { - /** - * URL to download or access the source video file. - */ - url: string; - } - - export interface Transformation { - /** - * Type of video transformation: - * - * - `video-transformation`: Standard video processing (resize, format conversion, - * etc.) - * - `gif-to-video`: Convert animated GIF to video format - * - `video-thumbnail`: Generate thumbnail image from video - */ - type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; - - /** - * Configuration options for video transformations. - */ - options?: Transformation.Options; - - /** - * Information about the transformed output video. - */ - output?: Transformation.Output; - } - - export namespace Transformation { - /** - * Configuration options for video transformations. - */ - export interface Options { - /** - * Audio codec used for encoding (aac or opus). - */ - audio_codec?: 'aac' | 'opus'; - - /** - * Whether to automatically rotate the video based on metadata. - */ - auto_rotate?: boolean; - - /** - * Output format for the transformed video or thumbnail. - */ - format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; - - /** - * Quality setting for the output video. - */ - quality?: number; - - /** - * Streaming protocol for adaptive bitrate streaming. - */ - stream_protocol?: 'HLS' | 'DASH'; - - /** - * Array of quality representations for adaptive bitrate streaming. - */ - variants?: Array; - - /** - * Video codec used for encoding (h264, vp9, or av1). - */ - video_codec?: 'h264' | 'vp9' | 'av1'; - } - - /** - * Information about the transformed output video. - */ - export interface Output { - /** - * URL to access the transformed video. - */ - url: string; - - /** - * Metadata of the output video file. - */ - video_metadata?: Output.VideoMetadata; - } - - export namespace Output { - /** - * Metadata of the output video file. - */ - export interface VideoMetadata { - /** - * Bitrate of the output video in bits per second. - */ - bitrate: number; - - /** - * Duration of the output video in seconds. - */ - duration: number; - - /** - * Height of the output video in pixels. - */ - height: number; - - /** - * Width of the output video in pixels. - */ - width: number; - } - } - } - } - - /** - * Information about the original request that triggered the video transformation. - */ export interface Request { /** - * Full URL of the transformation request that was submitted. + * The requested pre-transformation string. */ - url: string; + transformation: string; /** - * Unique identifier for the originating transformation request. + * Unique identifier for the originating request. */ x_request_id: string; - - /** - * User-Agent header from the original request that triggered the transformation. - */ - user_agent?: string; - } - - /** - * Performance metrics for the transformation process. - */ - export interface Timings { - /** - * Time spent downloading the source video from your origin or media library, in - * milliseconds. - */ - download_duration?: number; - - /** - * Time spent encoding the video, in milliseconds. - */ - encoding_duration?: number; } } @@ -507,7 +254,7 @@ export namespace VideoTransformationReadyEvent { * processed with the requested transformation and is now available in the Media * Library. */ -export interface UploadPreTransformSuccessWebhookEvent { +export interface UploadPreTransformSuccessEvent { /** * Unique identifier for the event. */ @@ -521,14 +268,14 @@ export interface UploadPreTransformSuccessWebhookEvent { /** * Object containing details of a successful upload. */ - data: UploadPreTransformSuccessWebhookEvent.Data; + data: UploadPreTransformSuccessEvent.Data; - request: UploadPreTransformSuccessWebhookEvent.Request; + request: UploadPreTransformSuccessEvent.Request; type: 'upload.pre-transform.success'; } -export namespace UploadPreTransformSuccessWebhookEvent { +export namespace UploadPreTransformSuccessEvent { /** * Object containing details of a successful upload. */ @@ -750,701 +497,481 @@ export namespace UploadPreTransformSuccessWebhookEvent { } /** - * Triggered when a pre-transformation fails. The file upload may have been - * accepted, but the requested transformation could not be applied. + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. */ -export interface UploadPreTransformErrorWebhookEvent { +export interface VideoTransformationAcceptedEvent { /** * Unique identifier for the event. */ id: string; /** - * Timestamp of when the event occurred in ISO8601 format. + * Timestamp when the event was created in ISO8601 format. */ created_at: string; - data: UploadPreTransformErrorWebhookEvent.Data; + data: VideoTransformationAcceptedEvent.Data; - request: UploadPreTransformErrorWebhookEvent.Request; + /** + * Information about the original request that triggered the video transformation. + */ + request: VideoTransformationAcceptedEvent.Request; - type: 'upload.pre-transform.error'; + type: 'video.transformation.accepted'; } -export namespace UploadPreTransformErrorWebhookEvent { +export namespace VideoTransformationAcceptedEvent { export interface Data { /** - * Name of the file. + * Information about the source video asset being transformed. */ - name: string; + asset: Data.Asset; /** - * Path of the file. + * Base information about a video transformation request. */ - path: string; - transformation: Data.Transformation; } export namespace Data { + /** + * Information about the source video asset being transformed. + */ + export interface Asset { + /** + * URL to download or access the source video file. + */ + url: string; + } + + /** + * Base information about a video transformation request. + */ export interface Transformation { - error: Transformation.Error; + /** + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video + */ + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; + + /** + * Configuration options for video transformations. + */ + options?: Transformation.Options; } export namespace Transformation { - export interface Error { + /** + * Configuration options for video transformations. + */ + export interface Options { /** - * Reason for the pre-transformation failure. + * Audio codec used for encoding (aac or opus). */ - reason: string; + audio_codec?: 'aac' | 'opus'; + + /** + * Whether to automatically rotate the video based on metadata. + */ + auto_rotate?: boolean; + + /** + * Output format for the transformed video or thumbnail. + */ + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + + /** + * Quality setting for the output video. + */ + quality?: number; + + /** + * Streaming protocol for adaptive bitrate streaming. + */ + stream_protocol?: 'HLS' | 'DASH'; + + /** + * Array of quality representations for adaptive bitrate streaming. + */ + variants?: Array; + + /** + * Video codec used for encoding (h264, vp9, or av1). + */ + video_codec?: 'h264' | 'vp9' | 'av1'; } } } + /** + * Information about the original request that triggered the video transformation. + */ export interface Request { /** - * The requested pre-transformation string. + * Full URL of the transformation request that was submitted. */ - transformation: string; + url: string; /** - * Unique identifier for the originating request. + * Unique identifier for the originating transformation request. */ x_request_id: string; + + /** + * User-Agent header from the original request that triggered the transformation. + */ + user_agent?: string; } } /** - * Triggered when a post-transformation completes successfully. The transformed - * version of the file is now ready and can be accessed via the provided URL. Note - * that each post-transformation generates a separate webhook event. + * Triggered when an error occurs during video encoding. Listen to this webhook to + * log error reasons and debug issues. Check your origin and URL endpoint settings + * if the reason is related to download failure. For other errors, contact ImageKit + * support. */ -export interface UploadPostTransformSuccessWebhookEvent { +export interface VideoTransformationErrorEvent { /** * Unique identifier for the event. */ id: string; /** - * Timestamp of when the event occurred in ISO8601 format. + * Timestamp when the event was created in ISO8601 format. */ created_at: string; - data: UploadPostTransformSuccessWebhookEvent.Data; + data: VideoTransformationErrorEvent.Data; - request: UploadPostTransformSuccessWebhookEvent.Request; + /** + * Information about the original request that triggered the video transformation. + */ + request: VideoTransformationErrorEvent.Request; - type: 'upload.post-transform.success'; + type: 'video.transformation.error'; } -export namespace UploadPostTransformSuccessWebhookEvent { +export namespace VideoTransformationErrorEvent { export interface Data { /** - * Unique identifier of the originally uploaded file. - */ - fileId: string; - - /** - * Name of the file. + * Information about the source video asset being transformed. */ - name: string; + asset: Data.Asset; - /** - * URL of the generated post-transformation. - */ - url: string; + transformation: Data.Transformation; } - export interface Request { - transformation: Request.Transformation; - + export namespace Data { /** - * Unique identifier for the originating request. + * Information about the source video asset being transformed. */ - x_request_id: string; - } + export interface Asset { + /** + * URL to download or access the source video file. + */ + url: string; + } - export namespace Request { export interface Transformation { /** - * Type of the requested post-transformation. + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video */ - type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; /** - * Only applicable if transformation type is 'abs'. Streaming protocol used. + * Details about the transformation error. */ - protocol?: 'hls' | 'dash'; + error?: Transformation.Error; /** - * Value for the requested transformation type. + * Configuration options for video transformations. */ - value?: string; + options?: Transformation.Options; + } + + export namespace Transformation { + /** + * Details about the transformation error. + */ + export interface Error { + /** + * Specific reason for the transformation failure: + * + * - `encoding_failed`: Error during video encoding process + * - `download_failed`: Could not download source video + * - `internal_server_error`: Unexpected server error + */ + reason: 'encoding_failed' | 'download_failed' | 'internal_server_error'; + } + + /** + * Configuration options for video transformations. + */ + export interface Options { + /** + * Audio codec used for encoding (aac or opus). + */ + audio_codec?: 'aac' | 'opus'; + + /** + * Whether to automatically rotate the video based on metadata. + */ + auto_rotate?: boolean; + + /** + * Output format for the transformed video or thumbnail. + */ + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; + + /** + * Quality setting for the output video. + */ + quality?: number; + + /** + * Streaming protocol for adaptive bitrate streaming. + */ + stream_protocol?: 'HLS' | 'DASH'; + + /** + * Array of quality representations for adaptive bitrate streaming. + */ + variants?: Array; + + /** + * Video codec used for encoding (h264, vp9, or av1). + */ + video_codec?: 'h264' | 'vp9' | 'av1'; + } } } + + /** + * Information about the original request that triggered the video transformation. + */ + export interface Request { + /** + * Full URL of the transformation request that was submitted. + */ + url: string; + + /** + * Unique identifier for the originating transformation request. + */ + x_request_id: string; + + /** + * User-Agent header from the original request that triggered the transformation. + */ + user_agent?: string; + } } /** - * Triggered when a post-transformation fails. The original file remains available, - * but the requested transformation could not be generated. + * Triggered when video encoding is finished and the transformed resource is ready + * to be served. This is the key event to listen for - update your database or CMS + * flags when you receive this so your application can start showing the + * transformed video to users. */ -export interface UploadPostTransformErrorWebhookEvent { +export interface VideoTransformationReadyEvent { /** * Unique identifier for the event. */ id: string; /** - * Timestamp of when the event occurred in ISO8601 format. + * Timestamp when the event was created in ISO8601 format. */ created_at: string; - data: UploadPostTransformErrorWebhookEvent.Data; + data: VideoTransformationReadyEvent.Data; + + /** + * Information about the original request that triggered the video transformation. + */ + request: VideoTransformationReadyEvent.Request; - request: UploadPostTransformErrorWebhookEvent.Request; + type: 'video.transformation.ready'; - type: 'upload.post-transform.error'; + /** + * Performance metrics for the transformation process. + */ + timings?: VideoTransformationReadyEvent.Timings; } -export namespace UploadPostTransformErrorWebhookEvent { +export namespace VideoTransformationReadyEvent { export interface Data { /** - * Unique identifier of the originally uploaded file. + * Information about the source video asset being transformed. */ - fileId: string; - - /** - * Name of the file. - */ - name: string; - - /** - * Path of the file. - */ - path: string; + asset: Data.Asset; transformation: Data.Transformation; - - /** - * URL of the attempted post-transformation. - */ - url: string; } export namespace Data { - export interface Transformation { - error: Transformation.Error; - } - - export namespace Transformation { - export interface Error { - /** - * Reason for the post-transformation failure. - */ - reason: string; - } - } - } - - export interface Request { - transformation: Request.Transformation; - /** - * Unique identifier for the originating request. + * Information about the source video asset being transformed. */ - x_request_id: string; - } + export interface Asset { + /** + * URL to download or access the source video file. + */ + url: string; + } - export namespace Request { export interface Transformation { /** - * Type of the requested post-transformation. + * Type of video transformation: + * + * - `video-transformation`: Standard video processing (resize, format conversion, + * etc.) + * - `gif-to-video`: Convert animated GIF to video format + * - `video-thumbnail`: Generate thumbnail image from video */ - type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; + type: 'video-transformation' | 'gif-to-video' | 'video-thumbnail'; /** - * Only applicable if transformation type is 'abs'. Streaming protocol used. + * Configuration options for video transformations. */ - protocol?: 'hls' | 'dash'; + options?: Transformation.Options; /** - * Value for the requested transformation type. + * Information about the transformed output video. */ - value?: string; + output?: Transformation.Output; } - } -} - -/** - * Triggered when a pre-transformation completes successfully. The file has been - * processed with the requested transformation and is now available in the Media - * Library. - */ -export interface UploadPreTransformSuccessWebhookEvent { - /** - * Unique identifier for the event. - */ - id: string; - - /** - * Timestamp of when the event occurred in ISO8601 format. - */ - created_at: string; - - /** - * Object containing details of a successful upload. - */ - data: UploadPreTransformSuccessWebhookEvent.Data; - request: UploadPreTransformSuccessWebhookEvent.Request; + export namespace Transformation { + /** + * Configuration options for video transformations. + */ + export interface Options { + /** + * Audio codec used for encoding (aac or opus). + */ + audio_codec?: 'aac' | 'opus'; - type: 'upload.pre-transform.success'; -} + /** + * Whether to automatically rotate the video based on metadata. + */ + auto_rotate?: boolean; -export namespace UploadPreTransformSuccessWebhookEvent { - /** - * Object containing details of a successful upload. - */ - export interface Data { - /** - * An array of tags assigned to the uploaded file by auto tagging. - */ - AITags?: Array | null; + /** + * Output format for the transformed video or thumbnail. + */ + format?: 'mp4' | 'webm' | 'jpg' | 'png' | 'webp'; - /** - * The audio codec used in the video (only for video). - */ - audioCodec?: string; + /** + * Quality setting for the output video. + */ + quality?: number; - /** - * The bit rate of the video in kbps (only for video). - */ - bitRate?: number; + /** + * Streaming protocol for adaptive bitrate streaming. + */ + stream_protocol?: 'HLS' | 'DASH'; - /** - * Value of custom coordinates associated with the image in the format - * `x,y,width,height`. If `customCoordinates` are not defined, then it is `null`. - * Send `customCoordinates` in `responseFields` in API request to get the value of - * this field. - */ - customCoordinates?: string | null; + /** + * Array of quality representations for adaptive bitrate streaming. + */ + variants?: Array; - /** - * A key-value data associated with the asset. Use `responseField` in API request - * to get `customMetadata` in the upload API response. Before setting any custom - * metadata on an asset, you have to create the field using custom metadata fields - * API. Send `customMetadata` in `responseFields` in API request to get the value - * of this field. - */ - customMetadata?: { [key: string]: unknown }; + /** + * Video codec used for encoding (h264, vp9, or av1). + */ + video_codec?: 'h264' | 'vp9' | 'av1'; + } - /** - * Optional text to describe the contents of the file. Can be set by the user or - * the ai-auto-description extension. - */ - description?: string; + /** + * Information about the transformed output video. + */ + export interface Output { + /** + * URL to access the transformed video. + */ + url: string; - /** - * The duration of the video in seconds (only for video). - */ - duration?: number; + /** + * Metadata of the output video file. + */ + video_metadata?: Output.VideoMetadata; + } - /** - * Consolidated embedded metadata associated with the file. It includes exif, iptc, - * and xmp data. Send `embeddedMetadata` in `responseFields` in API request to get - * embeddedMetadata in the upload API response. - */ - embeddedMetadata?: { [key: string]: unknown }; + export namespace Output { + /** + * Metadata of the output video file. + */ + export interface VideoMetadata { + /** + * Bitrate of the output video in bits per second. + */ + bitrate: number; - /** - * Extension names with their processing status at the time of completion of the - * request. It could have one of the following status values: - * - * `success`: The extension has been successfully applied. `failed`: The extension - * has failed and will not be retried. `pending`: The extension will finish - * processing in some time. On completion, the final status (success / failed) will - * be sent to the `webhookUrl` provided. - * - * If no extension was requested, then this parameter is not returned. - */ - extensionStatus?: Data.ExtensionStatus; + /** + * Duration of the output video in seconds. + */ + duration: number; - /** - * Unique fileId. Store this fileld in your database, as this will be used to - * perform update action on this file. - */ - fileId?: string; + /** + * Height of the output video in pixels. + */ + height: number; - /** - * The relative path of the file in the media library e.g. - * `/marketing-assets/new-banner.jpg`. - */ - filePath?: string; + /** + * Width of the output video in pixels. + */ + width: number; + } + } + } + } + /** + * Information about the original request that triggered the video transformation. + */ + export interface Request { /** - * Type of the uploaded file. Possible values are `image`, `non-image`. + * Full URL of the transformation request that was submitted. */ - fileType?: string; + url: string; /** - * Height of the image in pixels (Only for images) + * Unique identifier for the originating transformation request. */ - height?: number; + x_request_id: string; /** - * Is the file marked as private. It can be either `true` or `false`. Send - * `isPrivateFile` in `responseFields` in API request to get the value of this - * field. + * User-Agent header from the original request that triggered the transformation. */ - isPrivateFile?: boolean; + user_agent?: string; + } + /** + * Performance metrics for the transformation process. + */ + export interface Timings { /** - * Is the file published or in draft state. It can be either `true` or `false`. - * Send `isPublished` in `responseFields` in API request to get the value of this - * field. + * Time spent downloading the source video from your origin or media library, in + * milliseconds. */ - isPublished?: boolean; + download_duration?: number; /** - * Legacy metadata. Send `metadata` in `responseFields` in API request to get - * metadata in the upload API response. + * Time spent encoding the video, in milliseconds. */ - metadata?: FilesAPI.Metadata; - - /** - * Name of the asset. - */ - name?: string; - - /** - * Size of the image file in Bytes. - */ - size?: number; - - /** - * The array of tags associated with the asset. If no tags are set, it will be - * `null`. Send `tags` in `responseFields` in API request to get the value of this - * field. - */ - tags?: Array | null; - - /** - * In the case of an image, a small thumbnail URL. - */ - thumbnailUrl?: string; - - /** - * A publicly accessible URL of the file. - */ - url?: string; - - /** - * An object containing the file or file version's `id` (versionId) and `name`. - */ - versionInfo?: Data.VersionInfo; - - /** - * The video codec used in the video (only for video). - */ - videoCodec?: string; - - /** - * Width of the image in pixels (Only for Images) - */ - width?: number; - } - - export namespace Data { - export interface AITag { - /** - * Confidence score of the tag. - */ - confidence?: number; - - /** - * Name of the tag. - */ - name?: string; - - /** - * Array of `AITags` associated with the image. If no `AITags` are set, it will be - * null. These tags can be added using the `google-auto-tagging` or - * `aws-auto-tagging` extensions. - */ - source?: string; - } - - /** - * Extension names with their processing status at the time of completion of the - * request. It could have one of the following status values: - * - * `success`: The extension has been successfully applied. `failed`: The extension - * has failed and will not be retried. `pending`: The extension will finish - * processing in some time. On completion, the final status (success / failed) will - * be sent to the `webhookUrl` provided. - * - * If no extension was requested, then this parameter is not returned. - */ - export interface ExtensionStatus { - 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; - - 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; - - 'remove-bg'?: 'success' | 'pending' | 'failed'; - } - - /** - * An object containing the file or file version's `id` (versionId) and `name`. - */ - export interface VersionInfo { - /** - * Unique identifier of the file version. - */ - id?: string; - - /** - * Name of the file version. - */ - name?: string; - } - } - - export interface Request { - /** - * The requested pre-transformation string. - */ - transformation: string; - - /** - * Unique identifier for the originating request. - */ - x_request_id: string; - } -} - -/** - * Triggered when a pre-transformation fails. The file upload may have been - * accepted, but the requested transformation could not be applied. - */ -export interface UploadPreTransformErrorWebhookEvent { - /** - * Unique identifier for the event. - */ - id: string; - - /** - * Timestamp of when the event occurred in ISO8601 format. - */ - created_at: string; - - data: UploadPreTransformErrorWebhookEvent.Data; - - request: UploadPreTransformErrorWebhookEvent.Request; - - type: 'upload.pre-transform.error'; -} - -export namespace UploadPreTransformErrorWebhookEvent { - export interface Data { - /** - * Name of the file. - */ - name: string; - - /** - * Path of the file. - */ - path: string; - - transformation: Data.Transformation; - } - - export namespace Data { - export interface Transformation { - error: Transformation.Error; - } - - export namespace Transformation { - export interface Error { - /** - * Reason for the pre-transformation failure. - */ - reason: string; - } - } - } - - export interface Request { - /** - * The requested pre-transformation string. - */ - transformation: string; - - /** - * Unique identifier for the originating request. - */ - x_request_id: string; - } -} - -/** - * Triggered when a post-transformation completes successfully. The transformed - * version of the file is now ready and can be accessed via the provided URL. Note - * that each post-transformation generates a separate webhook event. - */ -export interface UploadPostTransformSuccessWebhookEvent { - /** - * Unique identifier for the event. - */ - id: string; - - /** - * Timestamp of when the event occurred in ISO8601 format. - */ - created_at: string; - - data: UploadPostTransformSuccessWebhookEvent.Data; - - request: UploadPostTransformSuccessWebhookEvent.Request; - - type: 'upload.post-transform.success'; -} - -export namespace UploadPostTransformSuccessWebhookEvent { - export interface Data { - /** - * Unique identifier of the originally uploaded file. - */ - fileId: string; - - /** - * Name of the file. - */ - name: string; - - /** - * URL of the generated post-transformation. - */ - url: string; - } - - export interface Request { - transformation: Request.Transformation; - - /** - * Unique identifier for the originating request. - */ - x_request_id: string; - } - - export namespace Request { - export interface Transformation { - /** - * Type of the requested post-transformation. - */ - type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; - - /** - * Only applicable if transformation type is 'abs'. Streaming protocol used. - */ - protocol?: 'hls' | 'dash'; - - /** - * Value for the requested transformation type. - */ - value?: string; - } - } -} - -/** - * Triggered when a post-transformation fails. The original file remains available, - * but the requested transformation could not be generated. - */ -export interface UploadPostTransformErrorWebhookEvent { - /** - * Unique identifier for the event. - */ - id: string; - - /** - * Timestamp of when the event occurred in ISO8601 format. - */ - created_at: string; - - data: UploadPostTransformErrorWebhookEvent.Data; - - request: UploadPostTransformErrorWebhookEvent.Request; - - type: 'upload.post-transform.error'; -} - -export namespace UploadPostTransformErrorWebhookEvent { - export interface Data { - /** - * Unique identifier of the originally uploaded file. - */ - fileId: string; - - /** - * Name of the file. - */ - name: string; - - /** - * Path of the file. - */ - path: string; - - transformation: Data.Transformation; - - /** - * URL of the attempted post-transformation. - */ - url: string; - } - - export namespace Data { - export interface Transformation { - error: Transformation.Error; - } - - export namespace Transformation { - export interface Error { - /** - * Reason for the post-transformation failure. - */ - reason: string; - } - } - } - - export interface Request { - transformation: Request.Transformation; - - /** - * Unique identifier for the originating request. - */ - x_request_id: string; - } - - export namespace Request { - export interface Transformation { - /** - * Type of the requested post-transformation. - */ - type: 'transformation' | 'abs' | 'gif-to-video' | 'thumbnail'; - - /** - * Only applicable if transformation type is 'abs'. Streaming protocol used. - */ - protocol?: 'hls' | 'dash'; - - /** - * Value for the requested transformation type. - */ - value?: string; - } + encoding_duration?: number; } } @@ -1457,10 +984,10 @@ export type UnsafeUnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent | VideoTransformationErrorEvent - | UploadPreTransformSuccessWebhookEvent - | UploadPreTransformErrorWebhookEvent - | UploadPostTransformSuccessWebhookEvent - | UploadPostTransformErrorWebhookEvent; + | UploadPreTransformSuccessEvent + | UploadPreTransformErrorEvent + | UploadPostTransformSuccessEvent + | UploadPostTransformErrorEvent; /** * Triggered when a new video transformation request is accepted for processing. @@ -1471,20 +998,20 @@ export type UnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent | VideoTransformationErrorEvent - | UploadPreTransformSuccessWebhookEvent - | UploadPreTransformErrorWebhookEvent - | UploadPostTransformSuccessWebhookEvent - | UploadPostTransformErrorWebhookEvent; + | UploadPreTransformSuccessEvent + | UploadPreTransformErrorEvent + | UploadPostTransformSuccessEvent + | UploadPostTransformErrorEvent; export declare namespace Webhooks { export { + type UploadPostTransformErrorEvent as UploadPostTransformErrorEvent, + type UploadPostTransformSuccessEvent as UploadPostTransformSuccessEvent, + type UploadPreTransformErrorEvent as UploadPreTransformErrorEvent, + type UploadPreTransformSuccessEvent as UploadPreTransformSuccessEvent, type VideoTransformationAcceptedEvent as VideoTransformationAcceptedEvent, type VideoTransformationErrorEvent as VideoTransformationErrorEvent, type VideoTransformationReadyEvent as VideoTransformationReadyEvent, - type UploadPreTransformSuccessWebhookEvent as UploadPreTransformSuccessWebhookEvent, - type UploadPreTransformErrorWebhookEvent as UploadPreTransformErrorWebhookEvent, - type UploadPostTransformSuccessWebhookEvent as UploadPostTransformSuccessWebhookEvent, - type UploadPostTransformErrorWebhookEvent as UploadPostTransformErrorWebhookEvent, type UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent, type UnwrapWebhookEvent as UnwrapWebhookEvent, }; From cfce32f9b706a52035714714dcbc8429e4072f04 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 15:35:37 +0530 Subject: [PATCH 22/73] feat: implement serializeUploadOptions function for upload option serialization and add tests --- src/lib/serialization-utils.ts | 42 ++++++++++++ src/resources/beta/v2/files.ts | 5 +- src/resources/files/files.ts | 5 +- tests/serialization-utils.test.ts | 109 ++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/lib/serialization-utils.ts create mode 100644 tests/serialization-utils.test.ts diff --git a/src/lib/serialization-utils.ts b/src/lib/serialization-utils.ts new file mode 100644 index 00000000..4e94ada5 --- /dev/null +++ b/src/lib/serialization-utils.ts @@ -0,0 +1,42 @@ +/** + * Serialize upload options to handle proper formatting for ImageKit backend API. + * Special cases handled: + * - tags: converted to comma-separated string + * - responseFields: converted to comma-separated string + * - extensions: JSON stringified + * - customMetadata: JSON stringified + * - transformation: JSON stringified + */ +export function serializeUploadOptions(uploadOptions: Record): Record { + const serialized: Record = { ...uploadOptions }; + + for (const key in serialized) { + if (key && serialized[key] !== undefined) { + const value = serialized[key]; + + if (key === 'tags' && Array.isArray(value)) { + // Tags should be comma-separated string + serialized[key] = value.join(','); + } else if (key === 'responseFields' && Array.isArray(value)) { + // Response fields should be comma-separated string + serialized[key] = value.join(','); + } else if (key === 'extensions' && Array.isArray(value)) { + // Extensions should be JSON stringified + serialized[key] = JSON.stringify(value); + } else if ( + key === 'customMetadata' && + typeof value === 'object' && + !Array.isArray(value) && + value !== null + ) { + // Custom metadata should be JSON stringified + serialized[key] = JSON.stringify(value); + } else if (key === 'transformation' && typeof value === 'object' && value !== null) { + // Transformation should be JSON stringified + serialized[key] = JSON.stringify(value); + } + } + } + + return serialized; +} diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts index 17bc06b2..36cdbde4 100644 --- a/src/resources/beta/v2/files.ts +++ b/src/resources/beta/v2/files.ts @@ -6,6 +6,7 @@ import { APIPromise } from '../../../core/api-promise'; import { type Uploadable } from '../../../core/uploads'; import { RequestOptions } from '../../../internal/request-options'; import { multipartFormRequestOptions } from '../../../internal/uploads'; +import { serializeUploadOptions } from '../../../lib/serialization-utils'; export class Files extends APIResource { /** @@ -46,10 +47,12 @@ export class Files extends APIResource { * ``` */ upload(body: FileUploadParams, options?: RequestOptions): APIPromise { + const serializedBody = serializeUploadOptions(body); + return this._client.post( '/api/v2/files/upload', multipartFormRequestOptions( - { body, defaultBaseURL: 'https://upload.imagekit.io', ...options }, + { body: serializedBody, defaultBaseURL: 'https://upload.imagekit.io', ...options }, this._client, ), ); diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 4e6b5a54..dd09d240 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -30,6 +30,7 @@ import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; import { multipartFormRequestOptions } from '../../internal/uploads'; import { path } from '../../internal/utils/path'; +import { serializeUploadOptions } from '../../lib/serialization-utils'; export class Files extends APIResource { bulk: BulkAPI.Bulk = new BulkAPI.Bulk(this._client); @@ -181,10 +182,12 @@ export class Files extends APIResource { * ``` */ upload(body: FileUploadParams, options?: RequestOptions): APIPromise { + const serializedBody = serializeUploadOptions(body); + return this._client.post( '/api/v1/files/upload', multipartFormRequestOptions( - { body, defaultBaseURL: 'https://upload.imagekit.io', ...options }, + { body: serializedBody, defaultBaseURL: 'https://upload.imagekit.io', ...options }, this._client, ), ); diff --git a/tests/serialization-utils.test.ts b/tests/serialization-utils.test.ts new file mode 100644 index 00000000..4f2ca2ef --- /dev/null +++ b/tests/serialization-utils.test.ts @@ -0,0 +1,109 @@ +import { serializeUploadOptions } from '../src/lib/serialization-utils'; + +describe('serializeUploadOptions', () => { + it('should serialize all special fields correctly while preserving other fields', () => { + const extensions = [ + { name: 'google-auto-tagging', maxTags: 10, minConfidence: 80 }, + { name: 'remove-bg', options: { bg_color: 'white' } }, + ]; + + const customMetadata = { + photographer: 'John Doe', + category: 'nature', + rating: 5, + }; + + const transformation = { + pre: 'w-500,h-300', + post: [ + { type: 'transformation', value: 'w-200,h-150' }, + { type: 'gif-to-video', value: 'q-80' }, + ], + }; + + const input = { + // Special fields that should be serialized + tags: ['nature', 'landscape', 'photography'], + responseFields: ['tags', 'customMetadata', 'isPrivateFile'], + extensions, + customMetadata, + transformation, + + // Regular fields that should remain unchanged + fileName: 'test-image.jpg', + folder: '/photos/2024', + isPrivateFile: false, + useUniqueFileName: true, + description: 'A beautiful landscape photo', + webhookUrl: 'https://example.com/webhook', + }; + + const result = serializeUploadOptions(input); + + // Assert special fields are properly serialized + expect(result['tags']).toBe('nature,landscape,photography'); + expect(result['responseFields']).toBe('tags,customMetadata,isPrivateFile'); + expect(result['extensions']).toBe(JSON.stringify(extensions)); + expect(result['customMetadata']).toBe(JSON.stringify(customMetadata)); + expect(result['transformation']).toBe(JSON.stringify(transformation)); + + // Assert regular fields remain unchanged + expect(result['fileName']).toBe('test-image.jpg'); + expect(result['folder']).toBe('/photos/2024'); + expect(result['isPrivateFile']).toBe(false); + expect(result['useUniqueFileName']).toBe(true); + expect(result['description']).toBe('A beautiful landscape photo'); + expect(result['webhookUrl']).toBe('https://example.com/webhook'); + + // Ensure original object is not modified + expect(input.tags).toEqual(['nature', 'landscape', 'photography']); + expect(input.extensions).toBe(extensions); + expect(input.customMetadata).toBe(customMetadata); + expect(input.transformation).toBe(transformation); + }); + + it('should handle edge cases with null, undefined, and empty values', () => { + const input = { + fileName: 'test.jpg', + + // undefined values + tags: undefined, + transformation: undefined, + + // null values + responseFields: null, + customMetadata: null, + + // empty arrays and objects + extensions: [], + emptyObject: {}, + emptyArray: [], + + // non-special arrays and objects should remain unchanged + regularArray: ['item1', 'item2'], + regularObject: { key: 'value' }, + }; + + const result = serializeUploadOptions(input); + + // undefined values should remain undefined + expect(result['tags']).toBeUndefined(); + expect(result['transformation']).toBeUndefined(); + + // null values should remain null + expect(result['responseFields']).toBeNull(); + expect(result['customMetadata']).toBeNull(); + + // empty arrays for special fields should be serialized + expect(result['extensions']).toBe('[]'); + + // empty arrays/objects for non-special fields should remain unchanged + expect(result['emptyObject']).toEqual({}); + expect(result['emptyArray']).toEqual([]); + expect(result['regularArray']).toEqual(['item1', 'item2']); + expect(result['regularObject']).toEqual({ key: 'value' }); + + // regular field should remain unchanged + expect(result['fileName']).toBe('test.jpg'); + }); +}); From 96c640d86b1810a122c8ba6418dd157cd0e1ff2d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:25:58 +0000 Subject: [PATCH 23/73] feat(api): add ai-auto-description field with status options to components schema --- .stats.yml | 4 ++-- src/resources/beta/v2/files.ts | 2 ++ src/resources/files/files.ts | 2 ++ src/resources/webhooks.ts | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6bb89db0..bd62087f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d857341f30517b11df568dd6c5a0e9dea3a854930f7f6583718114d311f2d5ee.yml -openapi_spec_hash: db94bfd556220d6244a1b2bbae9d7d00 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml +openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 config_hash: 4ef178e13ecfdb97211f284f13a21e83 diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts index 36cdbde4..bda12c3e 100644 --- a/src/resources/beta/v2/files.ts +++ b/src/resources/beta/v2/files.ts @@ -243,6 +243,8 @@ export namespace FileUploadResponse { * If no extension was requested, then this parameter is not returned. */ export interface ExtensionStatus { + 'ai-auto-description'?: 'success' | 'pending' | 'failed'; + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index dd09d240..940ce2b6 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -826,6 +826,8 @@ export namespace FileUploadResponse { * If no extension was requested, then this parameter is not returned. */ export interface ExtensionStatus { + 'ai-auto-description'?: 'success' | 'pending' | 'failed'; + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index cf6c7c4a..d1db98c0 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -460,6 +460,8 @@ export namespace UploadPreTransformSuccessEvent { * If no extension was requested, then this parameter is not returned. */ export interface ExtensionStatus { + 'ai-auto-description'?: 'success' | 'pending' | 'failed'; + 'aws-auto-tagging'?: 'success' | 'pending' | 'failed'; 'google-auto-tagging'?: 'success' | 'pending' | 'failed'; From 188eeee3b77d7e3a89e8c5abad4e0fef0ca9107f Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 16:09:01 +0530 Subject: [PATCH 24/73] feat(tests): add tests for transformation handling with absolute URLs and non-default endpoints --- tests/url-generation/basic.test.ts | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts index d464158e..f22a1a12 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/url-generation/basic.test.ts @@ -117,6 +117,39 @@ describe('URL generation', function () { expect(url).toBe(`https://ik.imagekit.io/test_url_endpoint/test_path.jpg?tr=h-300,w-400`); }); + it('should add transformation as query when src has absolute url even if transformationPosition is path', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', + transformationPosition: 'path', + src: 'https://my.custom.domain.com/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + // Now transformed URL goes into query since transformationPosition is "query". + expect(url).toBe(`https://my.custom.domain.com/test_path.jpg?tr=h-300,w-400`); + }); + + it('Handle non-default url-endpoint case', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/imagekit_id/new-endpoint/', + src: '/test_path.jpg', + transformation: [ + { + height: '300', + width: '400', + }, + ], + }); + + // Now transformed URL goes into query since transformationPosition is "query". + expect(url).toBe(`https://ik.imagekit.io/imagekit_id/new-endpoint/test_path.jpg?tr=h-300,w-400`); + }); + it('should generate the correct URL when the provided path contains multiple leading slashes', function () { const url = client.helper.buildSrc({ urlEndpoint: 'https://ik.imagekit.io/test_url_endpoint', From 2f37641776756aaae377c802f46e2ee6349127eb Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 16:17:17 +0530 Subject: [PATCH 25/73] feat(tests): add test for transformationPosition as path in signed URL generation --- tests/url-generation/signing.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/url-generation/signing.test.ts b/tests/url-generation/signing.test.ts index ffc53bd6..38afeef0 100644 --- a/tests/url-generation/signing.test.ts +++ b/tests/url-generation/signing.test.ts @@ -132,4 +132,21 @@ describe('URL Signing', function () { expect(url).not.toContain('ik-s='); expect(url).not.toContain('ik-t='); }); + + it('transformationPosition as path', function () { + const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/demo/', + src: 'sdk-testing-files/future-search.png', + transformation: [{ width: 300, height: 200 }], + transformationPosition: 'path', + queryParameters: { + version: '2.0', + }, + signed: true, + }); + + expect(url).toBe( + 'https://ik.imagekit.io/demo/tr:w-300,h-200/sdk-testing-files/future-search.png?version=2.0&ik-s=dd1ee8f83d019bc59fd57a5fc4674a11eb8a3496', + ); + }); }); From 7ab6b37f00f0b4ecba52bd4814370d22c5264c7e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:53:03 +0000 Subject: [PATCH 26/73] feat(docs): improve descriptions for private API key and password fields in client settings --- .stats.yml | 2 +- README.md | 4 ++-- src/client.ts | 12 ++++++++---- tests/index.test.ts | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index bd62087f..a4e72d08 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: 4ef178e13ecfdb97211f284f13a21e83 +config_hash: 7218b2df6efd609f88bac0ac591a70e9 diff --git a/README.md b/README.md index 65f81afd..5f46d231 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted - password: process.env['ORG_MY_PASSWORD_TOKEN'], // This is the default and can be omitted + password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); const response = await client.files.upload({ @@ -48,7 +48,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted - password: process.env['ORG_MY_PASSWORD_TOKEN'], // This is the default and can be omitted + password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); const params: ImageKit.FileUploadParams = { diff --git a/src/client.ts b/src/client.ts index d4ac7d3f..22bb5c02 100644 --- a/src/client.ts +++ b/src/client.ts @@ -86,12 +86,16 @@ import { isEmptyObj } from './internal/utils/values'; export interface ClientOptions { /** - * Your ImageKit private key starts with `private_`. + * Your ImageKit private API key (it starts with `private_`). + * You can view and manage API keys in the [dashboard](https://imagekit.io/dashboard/developer/api-keys). + * */ privateAPIKey?: string | undefined; /** - * Do not set this, its value is ignored + * ImageKit Basic Auth only uses the username field and ignores the password. + * This field is unused. + * */ password?: string | null | undefined; @@ -187,7 +191,7 @@ export class ImageKit { * API Client for interfacing with the Image Kit API. * * @param {string | undefined} [opts.privateAPIKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] - * @param {string | null | undefined} [opts.password=process.env['ORG_MY_PASSWORD_TOKEN'] ?? does_not_matter] + * @param {string | null | undefined} [opts.password=process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] ?? do_not_set] * @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API. * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. @@ -199,7 +203,7 @@ export class ImageKit { constructor({ baseURL = readEnv('IMAGE_KIT_BASE_URL'), privateAPIKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), - password = readEnv('ORG_MY_PASSWORD_TOKEN') ?? 'does_not_matter', + password = readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS') ?? 'do_not_set', ...opts }: ClientOptions = {}) { if (privateAPIKey === undefined) { diff --git a/tests/index.test.ts b/tests/index.test.ts index 37af5cd3..d3799f72 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -485,7 +485,7 @@ describe('instantiate client', () => { test('with environment variable arguments', () => { // set options via env var process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'My Private API Key'; - process.env['ORG_MY_PASSWORD_TOKEN'] = 'My Password'; + process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'My Password'; const client = new ImageKit(); expect(client.privateAPIKey).toBe('My Private API Key'); expect(client.password).toBe('My Password'); @@ -494,7 +494,7 @@ describe('instantiate client', () => { test('with overridden environment variable arguments', () => { // set options via env var process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'another My Private API Key'; - process.env['ORG_MY_PASSWORD_TOKEN'] = 'another My Password'; + process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'another My Password'; const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); expect(client.privateAPIKey).toBe('My Private API Key'); expect(client.password).toBe('My Password'); From 8b0bb87796dac17c9dd0a4ad43b198ad3f269763 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:44:47 +0000 Subject: [PATCH 27/73] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a4e72d08..93aeb615 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: 7218b2df6efd609f88bac0ac591a70e9 +config_hash: af15f7df8a4590c14cdce4460983aba6 From 636a5a991e4e648da2d183a6492e9a959938b2ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:05:53 +0000 Subject: [PATCH 28/73] feat(api): manual updates --- .stats.yml | 2 +- package.json | 4 +-- src/resources/webhooks.ts | 12 +------- tests/api-resources/webhooks.test.ts | 43 ---------------------------- yarn.lock | 18 ------------ 5 files changed, 3 insertions(+), 76 deletions(-) delete mode 100644 tests/api-resources/webhooks.test.ts diff --git a/.stats.yml b/.stats.yml index 93aeb615..8599545e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: af15f7df8a4590c14cdce4460983aba6 +config_hash: 84bf9f929b0248a6cdf2d526331ed8eb diff --git a/package.json b/package.json index ddb4b7de..9d1c2f88 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,7 @@ "lint": "./scripts/lint", "fix": "./scripts/format" }, - "dependencies": { - "standardwebhooks": "^1.0.0" - }, + "dependencies": {}, "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", "@swc/core": "^1.3.102", diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index d1db98c0..e27f66f1 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -2,23 +2,13 @@ import { APIResource } from '../core/resource'; import * as FilesAPI from './files/files'; -import { Webhook } from 'standardwebhooks'; export class Webhooks extends APIResource { unsafeUnwrap(body: string): UnsafeUnwrapWebhookEvent { return JSON.parse(body) as UnsafeUnwrapWebhookEvent; } - unwrap( - body: string, - { headers, key }: { headers: Record; key?: string }, - ): UnwrapWebhookEvent { - if (headers !== undefined) { - const keyStr: string | null = key === undefined ? this._client.privateAPIKey : key; - if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); - const wh = new Webhook(keyStr); - wh.verify(body, headers); - } + unwrap(body: string): UnwrapWebhookEvent { return JSON.parse(body) as UnwrapWebhookEvent; } } diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts deleted file mode 100644 index f3b1af6e..00000000 --- a/tests/api-resources/webhooks.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Webhook } from 'standardwebhooks'; - -import ImageKit from '@imagekit/nodejs'; - -const client = new ImageKit({ - privateAPIKey: 'My Private API Key', - password: 'My Password', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource webhooks', () => { - test.skip('unwrap', async () => { - const key = 'whsec_c2VjcmV0Cg=='; - const payload = - '{"id":"id","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"},"type":"video.transformation.accepted"}'; - const msgID = '1'; - const timestamp = new Date(); - const wh = new Webhook(key); - const signature = wh.sign(msgID, timestamp, payload); - const headers: Record = { - 'webhook-signature': signature, - 'webhook-id': msgID, - 'webhook-timestamp': String(Math.floor(timestamp.getTime() / 1000)), - }; - client.webhooks.unwrap(payload, { headers, key }); - expect(() => { - const wrongKey = 'whsec_aaaaaaaaaa=='; - client.webhooks.unwrap(payload, { headers, key: wrongKey }); - }).toThrow('No matching signature found'); - expect(() => { - const badSig = wh.sign(msgID, timestamp, 'some other payload'); - client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-signature': badSig }, key }); - }).toThrow('No matching signature found'); - expect(() => { - client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-timestamp': '5' }, key }); - }).toThrow('Message timestamp too old'); - expect(() => { - client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-id': 'wrong' }, key }); - }).toThrow('No matching signature found'); - }); -}); diff --git a/yarn.lock b/yarn.lock index 1935915b..8311caf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -743,11 +743,6 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@stablelib/base64@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be" - integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ== - "@swc/core-darwin-arm64@1.4.16": version "1.4.16" resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" @@ -1723,11 +1718,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-sha256@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6" - integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ== - fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -3111,14 +3101,6 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -standardwebhooks@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/standardwebhooks/-/standardwebhooks-1.0.0.tgz#5faa23ceacbf9accd344361101d9e3033b64324f" - integrity sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg== - dependencies: - "@stablelib/base64" "^1.0.0" - fast-sha256 "^1.3.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" From 1b740dfb1e21293568614f5a7fe96468762f5286 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:12:46 +0000 Subject: [PATCH 29/73] feat(api): manual updates --- .stats.yml | 2 +- package.json | 4 ++- src/client.ts | 15 ++++++++++ src/resources/webhooks.ts | 12 +++++++- tests/api-resources/webhooks.test.ts | 43 ++++++++++++++++++++++++++++ yarn.lock | 18 ++++++++++++ 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 tests/api-resources/webhooks.test.ts diff --git a/.stats.yml b/.stats.yml index 8599545e..28637701 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: 84bf9f929b0248a6cdf2d526331ed8eb +config_hash: 0f760028496146ece9431573b1ab0e46 diff --git a/package.json b/package.json index 9d1c2f88..ddb4b7de 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "lint": "./scripts/lint", "fix": "./scripts/format" }, - "dependencies": {}, + "dependencies": { + "standardwebhooks": "^1.0.0" + }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", "@swc/core": "^1.3.102", diff --git a/src/client.ts b/src/client.ts index 22bb5c02..bb1db01b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -99,6 +99,15 @@ export interface ClientOptions { */ password?: string | null | undefined; + /** + * Your ImageKit webhook secret. This is used by the SDK to verify webhook signatures. It starts with a `whsec_` prefix. + * You can view and manage your webhook secret in the [dashboard](https://imagekit.io/dashboard/developer/webhooks). + * Treat the secret like a password, keep it private and do not expose it publicly. + * Learn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature). + * + */ + webhookSecret?: string | null | undefined; + /** * Override the default base URL for the API, e.g., "https://api.example.com/v2/" * @@ -174,6 +183,7 @@ export interface ClientOptions { export class ImageKit { privateAPIKey: string; password: string | null; + webhookSecret: string | null; baseURL: string; maxRetries: number; @@ -192,6 +202,7 @@ export class ImageKit { * * @param {string | undefined} [opts.privateAPIKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] * @param {string | null | undefined} [opts.password=process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] ?? do_not_set] + * @param {string | null | undefined} [opts.webhookSecret=process.env['IMAGEKIT_WEBHOOK_SECRET'] ?? null] * @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API. * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. @@ -204,6 +215,7 @@ export class ImageKit { baseURL = readEnv('IMAGE_KIT_BASE_URL'), privateAPIKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), password = readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS') ?? 'do_not_set', + webhookSecret = readEnv('IMAGEKIT_WEBHOOK_SECRET') ?? null, ...opts }: ClientOptions = {}) { if (privateAPIKey === undefined) { @@ -215,6 +227,7 @@ export class ImageKit { const options: ClientOptions = { privateAPIKey, password, + webhookSecret, ...opts, baseURL: baseURL || `https://api.imagekit.io`, }; @@ -238,6 +251,7 @@ export class ImageKit { this.privateAPIKey = privateAPIKey; this.password = password; + this.webhookSecret = webhookSecret; } /** @@ -255,6 +269,7 @@ export class ImageKit { fetchOptions: this.fetchOptions, privateAPIKey: this.privateAPIKey, password: this.password, + webhookSecret: this.webhookSecret, ...options, }); return client; diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index e27f66f1..a1a1acf4 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -2,13 +2,23 @@ import { APIResource } from '../core/resource'; import * as FilesAPI from './files/files'; +import { Webhook } from 'standardwebhooks'; export class Webhooks extends APIResource { unsafeUnwrap(body: string): UnsafeUnwrapWebhookEvent { return JSON.parse(body) as UnsafeUnwrapWebhookEvent; } - unwrap(body: string): UnwrapWebhookEvent { + unwrap( + body: string, + { headers, key }: { headers: Record; key?: string }, + ): UnwrapWebhookEvent { + if (headers !== undefined) { + const keyStr: string | null = key === undefined ? this._client.webhookSecret : key; + if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); + const wh = new Webhook(keyStr); + wh.verify(body, headers); + } return JSON.parse(body) as UnwrapWebhookEvent; } } diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts new file mode 100644 index 00000000..f3b1af6e --- /dev/null +++ b/tests/api-resources/webhooks.test.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Webhook } from 'standardwebhooks'; + +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateAPIKey: 'My Private API Key', + password: 'My Password', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource webhooks', () => { + test.skip('unwrap', async () => { + const key = 'whsec_c2VjcmV0Cg=='; + const payload = + '{"id":"id","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"},"type":"video.transformation.accepted"}'; + const msgID = '1'; + const timestamp = new Date(); + const wh = new Webhook(key); + const signature = wh.sign(msgID, timestamp, payload); + const headers: Record = { + 'webhook-signature': signature, + 'webhook-id': msgID, + 'webhook-timestamp': String(Math.floor(timestamp.getTime() / 1000)), + }; + client.webhooks.unwrap(payload, { headers, key }); + expect(() => { + const wrongKey = 'whsec_aaaaaaaaaa=='; + client.webhooks.unwrap(payload, { headers, key: wrongKey }); + }).toThrow('No matching signature found'); + expect(() => { + const badSig = wh.sign(msgID, timestamp, 'some other payload'); + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-signature': badSig }, key }); + }).toThrow('No matching signature found'); + expect(() => { + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-timestamp': '5' }, key }); + }).toThrow('Message timestamp too old'); + expect(() => { + client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-id': 'wrong' }, key }); + }).toThrow('No matching signature found'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 8311caf5..1935915b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -743,6 +743,11 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@stablelib/base64@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be" + integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ== + "@swc/core-darwin-arm64@1.4.16": version "1.4.16" resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" @@ -1718,6 +1723,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-sha256@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6" + integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -3101,6 +3111,14 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +standardwebhooks@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/standardwebhooks/-/standardwebhooks-1.0.0.tgz#5faa23ceacbf9accd344361101d9e3033b64324f" + integrity sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg== + dependencies: + "@stablelib/base64" "^1.0.0" + fast-sha256 "^1.3.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" From 3d0571dbe9fa9cdd04f23a2f6d56a49005596649 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Mon, 1 Sep 2025 19:06:54 +0530 Subject: [PATCH 30/73] feat(webhooks): use toBase64 for webhook key in verification --- src/resources/webhooks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index a1a1acf4..6ff8f62b 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; +import { toBase64 } from '../internal/utils'; import * as FilesAPI from './files/files'; import { Webhook } from 'standardwebhooks'; @@ -16,7 +17,7 @@ export class Webhooks extends APIResource { if (headers !== undefined) { const keyStr: string | null = key === undefined ? this._client.webhookSecret : key; if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); - const wh = new Webhook(keyStr); + const wh = new Webhook(toBase64(keyStr)); wh.verify(body, headers); } return JSON.parse(body) as UnwrapWebhookEvent; From 8c90b7351bb330baf7d53b9f9fccf6d404a51ab5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:49:46 +0000 Subject: [PATCH 31/73] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 28637701..ad85869b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: 0f760028496146ece9431573b1ab0e46 +config_hash: cf9d50fe62973f4e91ef65c147aabcc1 From f243bc94b8a9d62137d0e270ca8bc8249675f17f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:50:13 +0000 Subject: [PATCH 32/73] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ad85869b..5d25590d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: cf9d50fe62973f4e91ef65c147aabcc1 +config_hash: 9f8a678d9d4d06daec522e8deb49e3ad From 13c716e35e73c8ad79157b818ac93b45365be8f3 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Tue, 2 Sep 2025 10:14:09 +0530 Subject: [PATCH 33/73] fix(webhooks): revert toBase64 conversion for webhook key --- src/resources/webhooks.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index 6ff8f62b..a1a1acf4 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; -import { toBase64 } from '../internal/utils'; import * as FilesAPI from './files/files'; import { Webhook } from 'standardwebhooks'; @@ -17,7 +16,7 @@ export class Webhooks extends APIResource { if (headers !== undefined) { const keyStr: string | null = key === undefined ? this._client.webhookSecret : key; if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); - const wh = new Webhook(toBase64(keyStr)); + const wh = new Webhook(keyStr); wh.verify(body, headers); } return JSON.parse(body) as UnwrapWebhookEvent; From 433eb44c54f3211d1b80aa97935a705ce7968a8a Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Tue, 2 Sep 2025 10:48:38 +0530 Subject: [PATCH 34/73] feat(webhooks): use toBase64 for webhook key in verification --- src/resources/webhooks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index a1a1acf4..6ff8f62b 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; +import { toBase64 } from '../internal/utils'; import * as FilesAPI from './files/files'; import { Webhook } from 'standardwebhooks'; @@ -16,7 +17,7 @@ export class Webhooks extends APIResource { if (headers !== undefined) { const keyStr: string | null = key === undefined ? this._client.webhookSecret : key; if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap'); - const wh = new Webhook(keyStr); + const wh = new Webhook(toBase64(keyStr)); wh.verify(body, headers); } return JSON.parse(body) as UnwrapWebhookEvent; From dac30e1479b5f022c0d59c0bd84ee928ba676dd2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 07:19:15 +0000 Subject: [PATCH 35/73] feat(api): add BaseWebhookEvent --- .stats.yml | 6 +++--- src/resources/webhooks.ts | 45 --------------------------------------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5d25590d..08185446 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml -openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886 -config_hash: 9f8a678d9d4d06daec522e8deb49e3ad +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml +openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a +config_hash: 4e73c7e12a531edcd1366dab7eccc360 diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index 6ff8f62b..cb63523b 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -24,10 +24,6 @@ export class Webhooks extends APIResource { } } -/** - * Triggered when a post-transformation fails. The original file remains available, - * but the requested transformation could not be generated. - */ export interface UploadPostTransformErrorEvent { /** * Unique identifier for the event. @@ -115,11 +111,6 @@ export namespace UploadPostTransformErrorEvent { } } -/** - * Triggered when a post-transformation completes successfully. The transformed - * version of the file is now ready and can be accessed via the provided URL. Note - * that each post-transformation generates a separate webhook event. - */ export interface UploadPostTransformSuccessEvent { /** * Unique identifier for the event. @@ -185,10 +176,6 @@ export namespace UploadPostTransformSuccessEvent { } } -/** - * Triggered when a pre-transformation fails. The file upload may have been - * accepted, but the requested transformation could not be applied. - */ export interface UploadPreTransformErrorEvent { /** * Unique identifier for the event. @@ -250,11 +237,6 @@ export namespace UploadPreTransformErrorEvent { } } -/** - * Triggered when a pre-transformation completes successfully. The file has been - * processed with the requested transformation and is now available in the Media - * Library. - */ export interface UploadPreTransformSuccessEvent { /** * Unique identifier for the event. @@ -499,11 +481,6 @@ export namespace UploadPreTransformSuccessEvent { } } -/** - * Triggered when a new video transformation request is accepted for processing. - * This event confirms that ImageKit has received and queued your transformation - * request. Use this for debugging and tracking transformation lifecycle. - */ export interface VideoTransformationAcceptedEvent { /** * Unique identifier for the event. @@ -633,12 +610,6 @@ export namespace VideoTransformationAcceptedEvent { } } -/** - * Triggered when an error occurs during video encoding. Listen to this webhook to - * log error reasons and debug issues. Check your origin and URL endpoint settings - * if the reason is related to download failure. For other errors, contact ImageKit - * support. - */ export interface VideoTransformationErrorEvent { /** * Unique identifier for the event. @@ -781,12 +752,6 @@ export namespace VideoTransformationErrorEvent { } } -/** - * Triggered when video encoding is finished and the transformed resource is ready - * to be served. This is the key event to listen for - update your database or CMS - * flags when you receive this so your application can start showing the - * transformed video to users. - */ export interface VideoTransformationReadyEvent { /** * Unique identifier for the event. @@ -978,11 +943,6 @@ export namespace VideoTransformationReadyEvent { } } -/** - * Triggered when a new video transformation request is accepted for processing. - * This event confirms that ImageKit has received and queued your transformation - * request. Use this for debugging and tracking transformation lifecycle. - */ export type UnsafeUnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent @@ -992,11 +952,6 @@ export type UnsafeUnwrapWebhookEvent = | UploadPostTransformSuccessEvent | UploadPostTransformErrorEvent; -/** - * Triggered when a new video transformation request is accepted for processing. - * This event confirms that ImageKit has received and queued your transformation - * request. Use this for debugging and tracking transformation lifecycle. - */ export type UnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent From 174eee861dac548093cc6b561eb59496cb5539cb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 07:20:44 +0000 Subject: [PATCH 36/73] feat(api): manual updates --- .stats.yml | 2 +- api.md | 1 + src/client.ts | 2 + src/resources/index.ts | 1 + src/resources/webhooks.ts | 97 +++++++++++++++++----------- tests/api-resources/webhooks.test.ts | 2 +- 6 files changed, 66 insertions(+), 39 deletions(-) diff --git a/.stats.yml b/.stats.yml index 08185446..52f982ba 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a -config_hash: 4e73c7e12a531edcd1366dab7eccc360 +config_hash: 08e12746cdca1c59c897cf2e50893c3c diff --git a/api.md b/api.md index 991d03ea..8e3ad709 100644 --- a/api.md +++ b/api.md @@ -208,6 +208,7 @@ Methods: Types: +- BaseWebhookEvent - UploadPostTransformErrorEvent - UploadPostTransformSuccessEvent - UploadPreTransformErrorEvent diff --git a/src/client.ts b/src/client.ts index bb1db01b..0e650a0d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -27,6 +27,7 @@ import { CustomMetadataFields, } from './resources/custom-metadata-fields'; import { + BaseWebhookEvent, UnsafeUnwrapWebhookEvent, UnwrapWebhookEvent, UploadPostTransformErrorEvent, @@ -895,6 +896,7 @@ export declare namespace ImageKit { export { Webhooks as Webhooks, + type BaseWebhookEvent as BaseWebhookEvent, type UploadPostTransformErrorEvent as UploadPostTransformErrorEvent, type UploadPostTransformSuccessEvent as UploadPostTransformSuccessEvent, type UploadPreTransformErrorEvent as UploadPreTransformErrorEvent, diff --git a/src/resources/index.ts b/src/resources/index.ts index d9fd9d97..39d0cc8b 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -45,6 +45,7 @@ export { } from './folders/folders'; export { Webhooks, + type BaseWebhookEvent, type UploadPostTransformErrorEvent, type UploadPostTransformSuccessEvent, type UploadPreTransformErrorEvent, diff --git a/src/resources/webhooks.ts b/src/resources/webhooks.ts index cb63523b..a95d7454 100644 --- a/src/resources/webhooks.ts +++ b/src/resources/webhooks.ts @@ -24,12 +24,23 @@ export class Webhooks extends APIResource { } } -export interface UploadPostTransformErrorEvent { +export interface BaseWebhookEvent { /** * Unique identifier for the event. */ id: string; + /** + * The type of webhook event. + */ + type: string; +} + +/** + * Triggered when a post-transformation fails. The original file remains available, + * but the requested transformation could not be generated. + */ +export interface UploadPostTransformErrorEvent extends BaseWebhookEvent { /** * Timestamp of when the event occurred in ISO8601 format. */ @@ -111,12 +122,12 @@ export namespace UploadPostTransformErrorEvent { } } -export interface UploadPostTransformSuccessEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when a post-transformation completes successfully. The transformed + * version of the file is now ready and can be accessed via the provided URL. Note + * that each post-transformation generates a separate webhook event. + */ +export interface UploadPostTransformSuccessEvent extends BaseWebhookEvent { /** * Timestamp of when the event occurred in ISO8601 format. */ @@ -176,12 +187,11 @@ export namespace UploadPostTransformSuccessEvent { } } -export interface UploadPreTransformErrorEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when a pre-transformation fails. The file upload may have been + * accepted, but the requested transformation could not be applied. + */ +export interface UploadPreTransformErrorEvent extends BaseWebhookEvent { /** * Timestamp of when the event occurred in ISO8601 format. */ @@ -237,12 +247,12 @@ export namespace UploadPreTransformErrorEvent { } } -export interface UploadPreTransformSuccessEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when a pre-transformation completes successfully. The file has been + * processed with the requested transformation and is now available in the Media + * Library. + */ +export interface UploadPreTransformSuccessEvent extends BaseWebhookEvent { /** * Timestamp of when the event occurred in ISO8601 format. */ @@ -481,12 +491,12 @@ export namespace UploadPreTransformSuccessEvent { } } -export interface VideoTransformationAcceptedEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ +export interface VideoTransformationAcceptedEvent extends BaseWebhookEvent { /** * Timestamp when the event was created in ISO8601 format. */ @@ -610,12 +620,13 @@ export namespace VideoTransformationAcceptedEvent { } } -export interface VideoTransformationErrorEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when an error occurs during video encoding. Listen to this webhook to + * log error reasons and debug issues. Check your origin and URL endpoint settings + * if the reason is related to download failure. For other errors, contact ImageKit + * support. + */ +export interface VideoTransformationErrorEvent extends BaseWebhookEvent { /** * Timestamp when the event was created in ISO8601 format. */ @@ -752,12 +763,13 @@ export namespace VideoTransformationErrorEvent { } } -export interface VideoTransformationReadyEvent { - /** - * Unique identifier for the event. - */ - id: string; - +/** + * Triggered when video encoding is finished and the transformed resource is ready + * to be served. This is the key event to listen for - update your database or CMS + * flags when you receive this so your application can start showing the + * transformed video to users. + */ +export interface VideoTransformationReadyEvent extends BaseWebhookEvent { /** * Timestamp when the event was created in ISO8601 format. */ @@ -943,6 +955,11 @@ export namespace VideoTransformationReadyEvent { } } +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ export type UnsafeUnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent @@ -952,6 +969,11 @@ export type UnsafeUnwrapWebhookEvent = | UploadPostTransformSuccessEvent | UploadPostTransformErrorEvent; +/** + * Triggered when a new video transformation request is accepted for processing. + * This event confirms that ImageKit has received and queued your transformation + * request. Use this for debugging and tracking transformation lifecycle. + */ export type UnwrapWebhookEvent = | VideoTransformationAcceptedEvent | VideoTransformationReadyEvent @@ -963,6 +985,7 @@ export type UnwrapWebhookEvent = export declare namespace Webhooks { export { + type BaseWebhookEvent as BaseWebhookEvent, type UploadPostTransformErrorEvent as UploadPostTransformErrorEvent, type UploadPostTransformSuccessEvent as UploadPostTransformSuccessEvent, type UploadPreTransformErrorEvent as UploadPreTransformErrorEvent, diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts index f3b1af6e..0d223f49 100644 --- a/tests/api-resources/webhooks.test.ts +++ b/tests/api-resources/webhooks.test.ts @@ -14,7 +14,7 @@ describe('resource webhooks', () => { test.skip('unwrap', async () => { const key = 'whsec_c2VjcmV0Cg=='; const payload = - '{"id":"id","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"},"type":"video.transformation.accepted"}'; + '{"id":"id","type":"video.transformation.accepted","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"}}'; const msgID = '1'; const timestamp = new Date(); const wh = new Webhook(key); From e86ea59d3125fa22ac4fde0b3520fe125b199c6a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:15:40 +0000 Subject: [PATCH 37/73] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 52f982ba..cfcbd7f7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a -config_hash: 08e12746cdca1c59c897cf2e50893c3c +config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c From 4efbfee0ca0de866a0ad77c607d7d6fb14a05c84 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:18:25 +0000 Subject: [PATCH 38/73] feat(api): manual updates --- .stats.yml | 4 ++-- README.md | 30 +++++++++++++++---------- src/resources/files/files.ts | 5 ++--- tests/api-resources/files/files.test.ts | 6 ++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/.stats.yml b/.stats.yml index cfcbd7f7..282d18eb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml -openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-eab9713fd85da68b41e881dfd9cd71f654e8620132da2023bab7dd01e5f7852e.yml +openapi_spec_hash: b5037b26fbe7360c6bfaf9947a65bb85 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/README.md b/README.md index 5f46d231..046fe55a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ const client = new ImageKit({ }); const response = await client.files.upload({ - file: fs.createReadStream('path/to/file'), + file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg', }); @@ -52,7 +52,7 @@ const client = new ImageKit({ }); const params: ImageKit.FileUploadParams = { - file: fs.createReadStream('path/to/file'), + file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg', }; const response: ImageKit.FileUploadResponse = await client.files.upload(params); @@ -76,17 +76,23 @@ import ImageKit, { toFile } from '@imagekit/nodejs'; const client = new ImageKit(); // If you have access to Node `fs` we recommend using `fs.createReadStream()`: -await client.files.upload({ file: fs.createReadStream('/path/to/file'), fileName: 'fileName' }); +await client.beta.v2.files.upload({ file: fs.createReadStream('/path/to/file'), fileName: 'fileName' }); // Or if you have the web `File` API you can pass a `File` instance: -await client.files.upload({ file: new File(['my bytes'], 'file'), fileName: 'fileName' }); +await client.beta.v2.files.upload({ file: new File(['my bytes'], 'file'), fileName: 'fileName' }); // You can also pass a `fetch` `Response`: -await client.files.upload({ file: await fetch('https://somesite/file'), fileName: 'fileName' }); +await client.beta.v2.files.upload({ file: await fetch('https://somesite/file'), fileName: 'fileName' }); // Finally, if none of the above are convenient, you can use our `toFile` helper: -await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), fileName: 'fileName' }); -await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); +await client.beta.v2.files.upload({ + file: await toFile(Buffer.from('my bytes'), 'file'), + fileName: 'fileName', +}); +await client.beta.v2.files.upload({ + file: await toFile(new Uint8Array([0, 1, 2]), 'file'), + fileName: 'fileName', +}); ``` ## URL generation @@ -295,7 +301,7 @@ a subclass of `APIError` will be thrown: ```ts const response = await client.files - .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) .catch(async (err) => { if (err instanceof ImageKit.APIError) { console.log(err.status); // 400 @@ -336,7 +342,7 @@ const client = new ImageKit({ }); // Or, configure per-request: -await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { +await client.files.upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }, { maxRetries: 5, }); ``` @@ -353,7 +359,7 @@ const client = new ImageKit({ }); // Override per-request: -await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { +await client.files.upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }, { timeout: 5 * 1000, }); ``` @@ -377,13 +383,13 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse const client = new ImageKit(); const response = await client.files - .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) .asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object const { data: response, response: raw } = await client.files - .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) + .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) .withResponse(); console.log(raw.headers.get('X-My-Header')); console.log(response.videoCodec); diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 940ce2b6..8f0bcf07 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -25,7 +25,6 @@ import { Versions, } from './versions'; import { APIPromise } from '../../core/api-promise'; -import { type Uploadable } from '../../core/uploads'; import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; import { multipartFormRequestOptions } from '../../internal/uploads'; @@ -176,7 +175,7 @@ export class Files extends APIResource { * @example * ```ts * const response = await client.files.upload({ - * file: fs.createReadStream('path/to/file'), + * file: 'https://www.example.com/path/to-image.jpg', * fileName: 'fileName', * }); * ``` @@ -1082,7 +1081,7 @@ export interface FileUploadParams { * When supplying a URL, the server must receive the response headers within 8 * seconds; otherwise the request fails with 400 Bad Request. */ - file: Uploadable; + file: string; /** * The name with which the file has to be uploaded. The file name can contain: diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index 8969d90b..515376ce 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import ImageKit, { toFile } from '@imagekit/nodejs'; +import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateAPIKey: 'My Private API Key', @@ -154,7 +154,7 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: only required params', async () => { const responsePromise = client.files.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), + file: 'https://www.example.com/path/to-image.jpg', fileName: 'fileName', }); const rawResponse = await responsePromise.asResponse(); @@ -169,7 +169,7 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: required and optional params', async () => { const response = await client.files.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), + file: 'https://www.example.com/path/to-image.jpg', fileName: 'fileName', token: 'token', checks: '"request.folder" : "marketing/"\n', From f70d1c2fc248efb16b990e047796bf7aab5387c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:21:19 +0000 Subject: [PATCH 39/73] feat(api): manual updates --- .stats.yml | 4 ++-- src/resources/files/files.ts | 2 +- tests/api-resources/files/files.test.ts | 7 ++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 282d18eb..7698a81b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-eab9713fd85da68b41e881dfd9cd71f654e8620132da2023bab7dd01e5f7852e.yml -openapi_spec_hash: b5037b26fbe7360c6bfaf9947a65bb85 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-911102f2eaf3be4b34381f6ba089330bed099bb72eb93667ce2f6e72b5934c8c.yml +openapi_spec_hash: 637ad417a580137914441bd790b04cc2 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 8f0bcf07..2ba228cc 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -175,7 +175,7 @@ export class Files extends APIResource { * @example * ```ts * const response = await client.files.upload({ - * file: 'https://www.example.com/path/to-image.jpg', + * file: 'file', * fileName: 'fileName', * }); * ``` diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index 515376ce..dbe91e55 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -153,10 +153,7 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: only required params', async () => { - const responsePromise = client.files.upload({ - file: 'https://www.example.com/path/to-image.jpg', - fileName: 'fileName', - }); + const responsePromise = client.files.upload({ file: 'file', fileName: 'fileName' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -169,7 +166,7 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: required and optional params', async () => { const response = await client.files.upload({ - file: 'https://www.example.com/path/to-image.jpg', + file: 'file', fileName: 'fileName', token: 'token', checks: '"request.folder" : "marketing/"\n', From 64fc45473e4072df18cff73024bcd4469258bf65 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:23:26 +0000 Subject: [PATCH 40/73] feat(api): manual updates --- .stats.yml | 4 ++-- README.md | 30 ++++++++++--------------- src/resources/files/files.ts | 5 +++-- tests/api-resources/files/files.test.ts | 9 +++++--- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7698a81b..d42605f1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-911102f2eaf3be4b34381f6ba089330bed099bb72eb93667ce2f6e72b5934c8c.yml -openapi_spec_hash: 637ad417a580137914441bd790b04cc2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0726d89b19f532c7fb92990d688a9bfb5aa4a9a871d64f49d4ba45bac6998e4a.yml +openapi_spec_hash: 9525bb02ab496b3459a1f93ac50968e3 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/README.md b/README.md index 046fe55a..5f46d231 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ const client = new ImageKit({ }); const response = await client.files.upload({ - file: 'https://www.example.com/public-url.jpg', + file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg', }); @@ -52,7 +52,7 @@ const client = new ImageKit({ }); const params: ImageKit.FileUploadParams = { - file: 'https://www.example.com/public-url.jpg', + file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg', }; const response: ImageKit.FileUploadResponse = await client.files.upload(params); @@ -76,23 +76,17 @@ import ImageKit, { toFile } from '@imagekit/nodejs'; const client = new ImageKit(); // If you have access to Node `fs` we recommend using `fs.createReadStream()`: -await client.beta.v2.files.upload({ file: fs.createReadStream('/path/to/file'), fileName: 'fileName' }); +await client.files.upload({ file: fs.createReadStream('/path/to/file'), fileName: 'fileName' }); // Or if you have the web `File` API you can pass a `File` instance: -await client.beta.v2.files.upload({ file: new File(['my bytes'], 'file'), fileName: 'fileName' }); +await client.files.upload({ file: new File(['my bytes'], 'file'), fileName: 'fileName' }); // You can also pass a `fetch` `Response`: -await client.beta.v2.files.upload({ file: await fetch('https://somesite/file'), fileName: 'fileName' }); +await client.files.upload({ file: await fetch('https://somesite/file'), fileName: 'fileName' }); // Finally, if none of the above are convenient, you can use our `toFile` helper: -await client.beta.v2.files.upload({ - file: await toFile(Buffer.from('my bytes'), 'file'), - fileName: 'fileName', -}); -await client.beta.v2.files.upload({ - file: await toFile(new Uint8Array([0, 1, 2]), 'file'), - fileName: 'fileName', -}); +await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), fileName: 'fileName' }); +await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); ``` ## URL generation @@ -301,7 +295,7 @@ a subclass of `APIError` will be thrown: ```ts const response = await client.files - .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) .catch(async (err) => { if (err instanceof ImageKit.APIError) { console.log(err.status); // 400 @@ -342,7 +336,7 @@ const client = new ImageKit({ }); // Or, configure per-request: -await client.files.upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }, { +await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { maxRetries: 5, }); ``` @@ -359,7 +353,7 @@ const client = new ImageKit({ }); // Override per-request: -await client.files.upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }, { +await client.files.upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }, { timeout: 5 * 1000, }); ``` @@ -383,13 +377,13 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse const client = new ImageKit(); const response = await client.files - .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) .asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object const { data: response, response: raw } = await client.files - .upload({ file: 'https://www.example.com/public-url.jpg', fileName: 'file-name.jpg' }) + .upload({ file: fs.createReadStream('path/to/file'), fileName: 'file-name.jpg' }) .withResponse(); console.log(raw.headers.get('X-My-Header')); console.log(response.videoCodec); diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 2ba228cc..940ce2b6 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -25,6 +25,7 @@ import { Versions, } from './versions'; import { APIPromise } from '../../core/api-promise'; +import { type Uploadable } from '../../core/uploads'; import { buildHeaders } from '../../internal/headers'; import { RequestOptions } from '../../internal/request-options'; import { multipartFormRequestOptions } from '../../internal/uploads'; @@ -175,7 +176,7 @@ export class Files extends APIResource { * @example * ```ts * const response = await client.files.upload({ - * file: 'file', + * file: fs.createReadStream('path/to/file'), * fileName: 'fileName', * }); * ``` @@ -1081,7 +1082,7 @@ export interface FileUploadParams { * When supplying a URL, the server must receive the response headers within 8 * seconds; otherwise the request fails with 400 Bad Request. */ - file: string; + file: Uploadable; /** * The name with which the file has to be uploaded. The file name can contain: diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index dbe91e55..8969d90b 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import ImageKit from '@imagekit/nodejs'; +import ImageKit, { toFile } from '@imagekit/nodejs'; const client = new ImageKit({ privateAPIKey: 'My Private API Key', @@ -153,7 +153,10 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: only required params', async () => { - const responsePromise = client.files.upload({ file: 'file', fileName: 'fileName' }); + const responsePromise = client.files.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + fileName: 'fileName', + }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -166,7 +169,7 @@ describe('resource files', () => { // Prism tests are disabled test.skip('upload: required and optional params', async () => { const response = await client.files.upload({ - file: 'file', + file: await toFile(Buffer.from('# my file contents'), 'README.md'), fileName: 'fileName', token: 'token', checks: '"request.folder" : "marketing/"\n', From 5977b7701fde87f78118bea1729eb719cd5859dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:32:39 +0000 Subject: [PATCH 41/73] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d42605f1..cfcbd7f7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0726d89b19f532c7fb92990d688a9bfb5aa4a9a871d64f49d4ba45bac6998e4a.yml -openapi_spec_hash: 9525bb02ab496b3459a1f93ac50968e3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml +openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c From 1d0423a6b3866f9ad2cf65a09d0e9f902930c37e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:03:09 +0000 Subject: [PATCH 42/73] feat(api): manual updates --- .stats.yml | 4 +- src/resources/files/files.ts | 920 ++++++++++++++++++++++++----------- 2 files changed, 645 insertions(+), 279 deletions(-) diff --git a/.stats.yml b/.stats.yml index cfcbd7f7..d458a07c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml -openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-e7bc47a9221d7da9c8c9653d3fd1d4cfdf2408588e32c4aa62bd02a50ec93c36.yml +openapi_spec_hash: 8d061396a2fff9d1add4d5baf03ab939 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 940ce2b6..f8d1621d 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1070,368 +1070,734 @@ export interface FileRenameParams { purgeCache?: boolean; } -export interface FileUploadParams { - /** - * The API accepts any of the following: - * - * - **Binary data** – send the raw bytes as `multipart/form-data`. - * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can - * fetch. - * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. - * - * When supplying a URL, the server must receive the response headers within 8 - * seconds; otherwise the request fails with 400 Bad Request. - */ - file: Uploadable; +export type FileUploadParams = FileUploadParams.FileUploadV1 | FileUploadParams.FileUploadV1ByURL; - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; - - /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. - */ - token?: string; +export declare namespace FileUploadParams { + export interface FileUploadV1 { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; - /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). - */ - checks?: string; + /** + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` + */ + fileName: string; - /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. - */ - customCoordinates?: string; + /** + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. + */ + token?: string; - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + */ + checks?: string; - /** - * Optional text to describe the contents of the file. - */ - description?: string; + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription - >; + /** + * Optional text to describe the contents of the file. + */ + description?: string; - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadV1.RemoveBg | FileUploadV1.AutoTaggingExtension | FileUploadV1.AIAutoDescription + >; - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; - /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. - */ - publicKey?: string; + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; - /** - * Array of response field keys to include in the API response body. - */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' - >; + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; - /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. - */ - tags?: Array; + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadParams.Transformation; + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; - /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. - */ - webhookUrl?: string; -} + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadV1.Transformation; -export namespace FileUploadParams { - export interface RemoveBg { /** - * Specifies the background removal extension. + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. */ - name: 'remove-bg'; + useUniqueFileName?: boolean; - options?: RemoveBg.Options; + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; } - export namespace RemoveBg { - export interface Options { + export namespace FileUploadV1 { + export interface RemoveBg { /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. + * Specifies the background removal extension. */ - add_shadow?: boolean; + name: 'remove-bg'; + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. + * Maximum number of tags to attach to the asset. */ - bg_color?: string; + maxTags: number; /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { + /** + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - bg_image_url?: string; + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - semitransparency?: boolean; + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } } } - export interface AutoTaggingExtension { + export interface FileUploadV1ByURL { /** - * Maximum number of tags to attach to the asset. + * A publicly reachable URL that ImageKit’s servers can fetch. The server must + * receive the response headers within 8 seconds; otherwise the request fails with + * 400 Bad Request. */ - maxTags: number; + file: string; /** - * Minimum confidence level for tags to be considered valid. + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` */ - minConfidence: number; + fileName: string; /** - * Specifies the auto-tagging extension used. + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } + token?: string; - export interface AIAutoDescription { /** - * Specifies the auto description extension. + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). */ - name: 'ai-auto-description'; - } + checks?: string; + + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; + + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + | FileUploadV1ByURL.RemoveBg + | FileUploadV1ByURL.AutoTaggingExtension + | FileUploadV1ByURL.AIAutoDescription >; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; + + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; + + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; + + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; + + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. */ - pre?: string; + overwriteCustomMetadata?: boolean; + + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; + + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; + + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; + + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; + + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; + + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadV1ByURL.Transformation; + + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; } - export namespace Transformation { - export interface Transformation { + export namespace FileUploadV1ByURL { + export interface RemoveBg { /** - * Transformation type. + * Specifies the background removal extension. */ - type: 'transformation'; + name: 'remove-bg'; - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; + options?: RemoveBg.Options; } - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + export interface AutoTaggingExtension { /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` + * Maximum number of tags to attach to the asset. */ - value?: string; - } + maxTags: number; - export interface Thumbnail { /** - * Generates a thumbnail image. + * Minimum confidence level for tags to be considered valid. */ - type: 'thumbnail'; + minConfidence: number; /** - * Optional transformation string. - * **Example**: `w-150,h-150` + * Specifies the auto-tagging extension used. */ - value?: string; + name: 'google-auto-tagging' | 'aws-auto-tagging'; } - export interface Abs { + export interface AIAutoDescription { /** - * Streaming protocol to use (`hls` or `dash`). + * Specifies the auto description extension. */ - protocol: 'hls' | 'dash'; + name: 'ai-auto-description'; + } + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { /** - * Adaptive Bitrate Streaming (ABS) setup. + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - type: 'abs'; + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; /** - * List of different representations you want to create separated by an underscore. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - value: string; + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } } } } From 50c8520ab96f5e96dcb50ca3964be1f21acd1dec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:10:39 +0000 Subject: [PATCH 43/73] feat(api): manual updates --- .stats.yml | 4 +- src/resources/files/files.ts | 920 +++++++++++------------------------ 2 files changed, 279 insertions(+), 645 deletions(-) diff --git a/.stats.yml b/.stats.yml index d458a07c..cfcbd7f7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-e7bc47a9221d7da9c8c9653d3fd1d4cfdf2408588e32c4aa62bd02a50ec93c36.yml -openapi_spec_hash: 8d061396a2fff9d1add4d5baf03ab939 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml +openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index f8d1621d..940ce2b6 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1070,734 +1070,368 @@ export interface FileRenameParams { purgeCache?: boolean; } -export type FileUploadParams = FileUploadParams.FileUploadV1 | FileUploadParams.FileUploadV1ByURL; +export interface FileUploadParams { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; -export declare namespace FileUploadParams { - export interface FileUploadV1 { - /** - * The API accepts any of the following: - * - * - **Binary data** – send the raw bytes as `multipart/form-data`. - * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can - * fetch. - * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. - * - * When supplying a URL, the server must receive the response headers within 8 - * seconds; otherwise the request fails with 400 Bad Request. - */ - file: Uploadable; + /** + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` + */ + fileName: string; - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; + /** + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. + */ + token?: string; - /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. - */ - token?: string; + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + */ + checks?: string; - /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). - */ - checks?: string; + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. - */ - customCoordinates?: string; + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; + /** + * Optional text to describe the contents of the file. + */ + description?: string; - /** - * Optional text to describe the contents of the file. - */ - description?: string; + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription + >; - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - FileUploadV1.RemoveBg | FileUploadV1.AutoTaggingExtension | FileUploadV1.AIAutoDescription - >; + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; - /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. - */ - publicKey?: string; + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; - /** - * Array of response field keys to include in the API response body. - */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' - >; + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; - /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. - */ - tags?: Array; + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadParams.Transformation; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadV1.Transformation; + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; +} +export namespace FileUploadParams { + export interface RemoveBg { /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. + * Specifies the background removal extension. */ - webhookUrl?: string; - } + name: 'remove-bg'; - export namespace FileUploadV1 { - export interface RemoveBg { - /** - * Specifies the background removal extension. - */ - name: 'remove-bg'; - - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } - } + options?: RemoveBg.Options; + } - export interface AutoTaggingExtension { + export namespace RemoveBg { + export interface Options { /** - * Maximum number of tags to attach to the asset. + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. */ - maxTags: number; + add_shadow?: boolean; /** - * Minimum confidence level for tags to be considered valid. + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. */ - minConfidence: number; + bg_color?: string; /** - * Specifies the auto-tagging extension used. - */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } - - export interface AIAutoDescription { - /** - * Specifies the auto description extension. - */ - name: 'ai-auto-description'; - } - - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { - /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs - >; + bg_image_url?: string; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. */ - pre?: string; - } - - export namespace Transformation { - export interface Transformation { - /** - * Transformation type. - */ - type: 'transformation'; - - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; - } - - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; - - /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` - */ - value?: string; - } - - export interface Thumbnail { - /** - * Generates a thumbnail image. - */ - type: 'thumbnail'; - - /** - * Optional transformation string. - * **Example**: `w-150,h-150` - */ - value?: string; - } - - export interface Abs { - /** - * Streaming protocol to use (`hls` or `dash`). - */ - protocol: 'hls' | 'dash'; - - /** - * Adaptive Bitrate Streaming (ABS) setup. - */ - type: 'abs'; - - /** - * List of different representations you want to create separated by an underscore. - */ - value: string; - } + semitransparency?: boolean; } } - export interface FileUploadV1ByURL { - /** - * A publicly reachable URL that ImageKit’s servers can fetch. The server must - * receive the response headers within 8 seconds; otherwise the request fails with - * 400 Bad Request. - */ - file: string; - - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; - + export interface AutoTaggingExtension { /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. + * Maximum number of tags to attach to the asset. */ - token?: string; + maxTags: number; /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + * Minimum confidence level for tags to be considered valid. */ - checks?: string; + minConfidence: number; /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. + * Specifies the auto-tagging extension used. */ - customCoordinates?: string; - - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; - - /** - * Optional text to describe the contents of the file. - */ - description?: string; - - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; - - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - | FileUploadV1ByURL.RemoveBg - | FileUploadV1ByURL.AutoTaggingExtension - | FileUploadV1ByURL.AIAutoDescription - >; - - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; - - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; - - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; - - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; - - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; - - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; - - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + export interface AIAutoDescription { /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. + * Specifies the auto description extension. */ - publicKey?: string; + name: 'ai-auto-description'; + } + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { /** - * Array of response field keys to include in the API response body. + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs >; /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; - - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - tags?: Array; - - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadV1ByURL.Transformation; - - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; - - /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. - */ - webhookUrl?: string; + pre?: string; } - export namespace FileUploadV1ByURL { - export interface RemoveBg { + export namespace Transformation { + export interface Transformation { /** - * Specifies the background removal extension. + * Transformation type. */ - name: 'remove-bg'; + type: 'transformation'; - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; } - export interface AutoTaggingExtension { + export interface GifToVideo { /** - * Maximum number of tags to attach to the asset. + * Converts an animated GIF into an MP4. */ - maxTags: number; + type: 'gif-to-video'; /** - * Minimum confidence level for tags to be considered valid. + * Optional transformation string to apply to the output video. + * **Example**: `q-80` */ - minConfidence: number; + value?: string; + } + export interface Thumbnail { /** - * Specifies the auto-tagging extension used. + * Generates a thumbnail image. */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } + type: 'thumbnail'; - export interface AIAutoDescription { /** - * Specifies the auto description extension. + * Optional transformation string. + * **Example**: `w-150,h-150` */ - name: 'ai-auto-description'; + value?: string; } - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { + export interface Abs { /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * Streaming protocol to use (`hls` or `dash`). */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs - >; + protocol: 'hls' | 'dash'; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * Adaptive Bitrate Streaming (ABS) setup. */ - pre?: string; - } - - export namespace Transformation { - export interface Transformation { - /** - * Transformation type. - */ - type: 'transformation'; - - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; - } + type: 'abs'; - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; - - /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` - */ - value?: string; - } - - export interface Thumbnail { - /** - * Generates a thumbnail image. - */ - type: 'thumbnail'; - - /** - * Optional transformation string. - * **Example**: `w-150,h-150` - */ - value?: string; - } - - export interface Abs { - /** - * Streaming protocol to use (`hls` or `dash`). - */ - protocol: 'hls' | 'dash'; - - /** - * Adaptive Bitrate Streaming (ABS) setup. - */ - type: 'abs'; - - /** - * List of different representations you want to create separated by an underscore. - */ - value: string; - } + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; } } } From dc932e36e7d79742e2d1d39a8a4aaa7b667b85c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:40:58 +0000 Subject: [PATCH 44/73] feat(api): manual updates --- .stats.yml | 4 +- src/resources/files/files.ts | 920 ++++++++++++++++++++++++----------- 2 files changed, 645 insertions(+), 279 deletions(-) diff --git a/.stats.yml b/.stats.yml index cfcbd7f7..db7d8f1e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml -openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9ae7b43dcfd6208ca37c32c887630ae186ec338bcdd36902b6fe5d1cc66459dc.yml +openapi_spec_hash: 25fb64c067e64bcff6eaaabda26de397 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 940ce2b6..797c1add 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1070,368 +1070,734 @@ export interface FileRenameParams { purgeCache?: boolean; } -export interface FileUploadParams { - /** - * The API accepts any of the following: - * - * - **Binary data** – send the raw bytes as `multipart/form-data`. - * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can - * fetch. - * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. - * - * When supplying a URL, the server must receive the response headers within 8 - * seconds; otherwise the request fails with 400 Bad Request. - */ - file: Uploadable; +export type FileUploadParams = FileUploadParams.FileUploadV1 | FileUploadParams.FileUploadByUrlv1; - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; - - /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. - */ - token?: string; +export declare namespace FileUploadParams { + export interface FileUploadV1 { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; - /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). - */ - checks?: string; + /** + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` + */ + fileName: string; - /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. - */ - customCoordinates?: string; + /** + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. + */ + token?: string; - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + */ + checks?: string; - /** - * Optional text to describe the contents of the file. - */ - description?: string; + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription - >; + /** + * Optional text to describe the contents of the file. + */ + description?: string; - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadV1.RemoveBg | FileUploadV1.AutoTaggingExtension | FileUploadV1.AIAutoDescription + >; - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; - /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. - */ - publicKey?: string; + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; - /** - * Array of response field keys to include in the API response body. - */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' - >; + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; - /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. - */ - tags?: Array; + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadParams.Transformation; + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; - /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. - */ - webhookUrl?: string; -} + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadV1.Transformation; -export namespace FileUploadParams { - export interface RemoveBg { /** - * Specifies the background removal extension. + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. */ - name: 'remove-bg'; + useUniqueFileName?: boolean; - options?: RemoveBg.Options; + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; } - export namespace RemoveBg { - export interface Options { + export namespace FileUploadV1 { + export interface RemoveBg { /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. + * Specifies the background removal extension. */ - add_shadow?: boolean; + name: 'remove-bg'; + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. + * Maximum number of tags to attach to the asset. */ - bg_color?: string; + maxTags: number; /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { + /** + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - bg_image_url?: string; + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - semitransparency?: boolean; + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } } } - export interface AutoTaggingExtension { + export interface FileUploadByUrlv1 { /** - * Maximum number of tags to attach to the asset. + * The URL of the file to upload. A publicly reachable URL that ImageKit servers + * can fetch. The server must receive the response headers within 8 seconds; + * otherwise the request fails with 400 Bad Request. */ - maxTags: number; + file: string; /** - * Minimum confidence level for tags to be considered valid. + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` */ - minConfidence: number; + fileName: string; /** - * Specifies the auto-tagging extension used. + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } + token?: string; - export interface AIAutoDescription { /** - * Specifies the auto description extension. + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). */ - name: 'ai-auto-description'; - } + checks?: string; + + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; + + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + | FileUploadByUrlv1.RemoveBg + | FileUploadByUrlv1.AutoTaggingExtension + | FileUploadByUrlv1.AIAutoDescription >; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; + + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; + + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; + + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; + + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. */ - pre?: string; + overwriteCustomMetadata?: boolean; + + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; + + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; + + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; + + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; + + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; + + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; + + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadByUrlv1.Transformation; + + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; } - export namespace Transformation { - export interface Transformation { + export namespace FileUploadByUrlv1 { + export interface RemoveBg { /** - * Transformation type. + * Specifies the background removal extension. */ - type: 'transformation'; + name: 'remove-bg'; - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; + options?: RemoveBg.Options; } - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + export interface AutoTaggingExtension { /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` + * Maximum number of tags to attach to the asset. */ - value?: string; - } + maxTags: number; - export interface Thumbnail { /** - * Generates a thumbnail image. + * Minimum confidence level for tags to be considered valid. */ - type: 'thumbnail'; + minConfidence: number; /** - * Optional transformation string. - * **Example**: `w-150,h-150` + * Specifies the auto-tagging extension used. */ - value?: string; + name: 'google-auto-tagging' | 'aws-auto-tagging'; } - export interface Abs { + export interface AIAutoDescription { /** - * Streaming protocol to use (`hls` or `dash`). + * Specifies the auto description extension. */ - protocol: 'hls' | 'dash'; + name: 'ai-auto-description'; + } + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { /** - * Adaptive Bitrate Streaming (ABS) setup. + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - type: 'abs'; + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs + >; /** - * List of different representations you want to create separated by an underscore. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - value: string; + pre?: string; + } + + export namespace Transformation { + export interface Transformation { + /** + * Transformation type. + */ + type: 'transformation'; + + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; + } + + export interface GifToVideo { + /** + * Converts an animated GIF into an MP4. + */ + type: 'gif-to-video'; + + /** + * Optional transformation string to apply to the output video. + * **Example**: `q-80` + */ + value?: string; + } + + export interface Thumbnail { + /** + * Generates a thumbnail image. + */ + type: 'thumbnail'; + + /** + * Optional transformation string. + * **Example**: `w-150,h-150` + */ + value?: string; + } + + export interface Abs { + /** + * Streaming protocol to use (`hls` or `dash`). + */ + protocol: 'hls' | 'dash'; + + /** + * Adaptive Bitrate Streaming (ABS) setup. + */ + type: 'abs'; + + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; + } } } } From 679d8778de67f29afb5dbc8affc7f1c6630292ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:45:07 +0000 Subject: [PATCH 45/73] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index db7d8f1e..f8166f44 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9ae7b43dcfd6208ca37c32c887630ae186ec338bcdd36902b6fe5d1cc66459dc.yml -openapi_spec_hash: 25fb64c067e64bcff6eaaabda26de397 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-5ce78cb448cc4520f5fbcc753452e0237b50a4bf64902e0548a8ad24bbdc82cf.yml +openapi_spec_hash: fd8ac4c2cdddc3d3a0b0c81be6a9edfe config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c From 9d913fa2de488eed9e5be5d4ec10b5ad83335c62 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 05:43:30 +0000 Subject: [PATCH 46/73] feat(api): manual updates --- .stats.yml | 4 +- src/resources/files/files.ts | 920 +++++++++++------------------------ 2 files changed, 279 insertions(+), 645 deletions(-) diff --git a/.stats.yml b/.stats.yml index f8166f44..cfcbd7f7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-5ce78cb448cc4520f5fbcc753452e0237b50a4bf64902e0548a8ad24bbdc82cf.yml -openapi_spec_hash: fd8ac4c2cdddc3d3a0b0c81be6a9edfe +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml +openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 797c1add..940ce2b6 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1070,734 +1070,368 @@ export interface FileRenameParams { purgeCache?: boolean; } -export type FileUploadParams = FileUploadParams.FileUploadV1 | FileUploadParams.FileUploadByUrlv1; +export interface FileUploadParams { + /** + * The API accepts any of the following: + * + * - **Binary data** – send the raw bytes as `multipart/form-data`. + * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can + * fetch. + * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. + * + * When supplying a URL, the server must receive the response headers within 8 + * seconds; otherwise the request fails with 400 Bad Request. + */ + file: Uploadable; -export declare namespace FileUploadParams { - export interface FileUploadV1 { - /** - * The API accepts any of the following: - * - * - **Binary data** – send the raw bytes as `multipart/form-data`. - * - **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can - * fetch. - * - **Base64 string** – the file encoded as a Base64 data URI or plain Base64. - * - * When supplying a URL, the server must receive the response headers within 8 - * seconds; otherwise the request fails with 400 Bad Request. - */ - file: Uploadable; + /** + * The name with which the file has to be uploaded. The file name can contain: + * + * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. + * - Special Characters: `.`, `-` + * + * Any other character including space will be replaced by `_` + */ + fileName: string; - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; + /** + * A unique value that the ImageKit.io server will use to recognize and prevent + * subsequent retries for the same request. We suggest using V4 UUIDs, or another + * random string with enough entropy to avoid collisions. This field is only + * required for authentication when uploading a file from the client side. + * + * **Note**: Sending a value that has been used in the past will result in a + * validation error. Even if your previous request resulted in an error, you should + * always send a new value for this field. + */ + token?: string; - /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. - */ - token?: string; + /** + * Server-side checks to run on the asset. Read more about + * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + */ + checks?: string; - /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). - */ - checks?: string; + /** + * Define an important area in the image. This is only relevant for image type + * files. + * + * - To be passed as a string with the x and y coordinates of the top-left corner, + * and width and height of the area of interest in the format `x,y,width,height`. + * For example - `10,10,100,100` + * - Can be used with fo-customtransformation. + * - If this field is not specified and the file is overwritten, then + * customCoordinates will be removed. + */ + customCoordinates?: string; - /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. - */ - customCoordinates?: string; + /** + * JSON key-value pairs to associate with the asset. Create the custom metadata + * fields before setting these values. + */ + customMetadata?: { [key: string]: unknown }; - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; + /** + * Optional text to describe the contents of the file. + */ + description?: string; - /** - * Optional text to describe the contents of the file. - */ - description?: string; + /** + * The time until your signature is valid. It must be a + * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into + * the future. It should be in seconds. This field is only required for + * authentication when uploading a file from the client side. + */ + expire?: number; - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; + /** + * Array of extensions to be applied to the image. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Array< + FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription + >; - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - FileUploadV1.RemoveBg | FileUploadV1.AutoTaggingExtension | FileUploadV1.AIAutoDescription - >; + /** + * The folder path in which the image has to be uploaded. If the folder(s) didn't + * exist before, a new folder(s) is created. + * + * The folder name can contain: + * + * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` + * - Special Characters: `/` , `_` , `-` + * + * Using multiple `/` creates a nested folder. + */ + folder?: string; - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; + /** + * Whether to mark the file as private or not. + * + * If `true`, the file is marked as private and is accessible only using named + * transformation or signed URL. + */ + isPrivateFile?: boolean; - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; + /** + * Whether to upload file as published or not. + * + * If `false`, the file is marked as unpublished, which restricts access to the + * file only via the media library. Files in draft or unpublished state can only be + * publicly accessed after being published. + * + * The option to upload in draft state is only available in custom enterprise + * pricing plans. + */ + isPublished?: boolean; - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; + /** + * If set to `true` and a file already exists at the exact location, its AITags + * will be removed. Set `overwriteAITags` to `false` to preserve AITags. + */ + overwriteAITags?: boolean; - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; + /** + * If the request does not have `customMetadata`, and a file already exists at the + * exact location, existing customMetadata will be removed. + */ + overwriteCustomMetadata?: boolean; - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; + /** + * If `false` and `useUniqueFileName` is also `false`, and a file already exists at + * the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean; - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; + /** + * If the request does not have `tags`, and a file already exists at the exact + * location, existing tags will be removed. + */ + overwriteTags?: boolean; - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + /** + * Your ImageKit.io public key. This field is only required for authentication when + * uploading a file from the client side. + */ + publicKey?: string; - /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. - */ - publicKey?: string; + /** + * Array of response field keys to include in the API response body. + */ + responseFields?: Array< + | 'tags' + | 'customCoordinates' + | 'isPrivateFile' + | 'embeddedMetadata' + | 'isPublished' + | 'customMetadata' + | 'metadata' + >; - /** - * Array of response field keys to include in the API response body. - */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' - >; + /** + * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a + * key. Learn how to create a signature on the page below. This should be in + * lowercase. + * + * Signature must be calculated on the server-side. This field is only required for + * authentication when uploading a file from the client side. + */ + signature?: string; - /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; + /** + * Set the tags while uploading the file. Provide an array of tag strings (e.g. + * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not + * exceed 500, and the `%` character is not allowed. If this field is not specified + * and the file is overwritten, the existing tags will be removed. + */ + tags?: Array; - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. - */ - tags?: Array; + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + transformation?: FileUploadParams.Transformation; - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadV1.Transformation; + /** + * Whether to use a unique filename for this file or not. + * + * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get + * a unique filename. + * + * If `false`, then the image is uploaded with the provided filename parameter, and + * any existing file with the same name is replaced. + */ + useUniqueFileName?: boolean; - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; +} +export namespace FileUploadParams { + export interface RemoveBg { /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. + * Specifies the background removal extension. */ - webhookUrl?: string; - } + name: 'remove-bg'; - export namespace FileUploadV1 { - export interface RemoveBg { - /** - * Specifies the background removal extension. - */ - name: 'remove-bg'; - - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } - } + options?: RemoveBg.Options; + } - export interface AutoTaggingExtension { + export namespace RemoveBg { + export interface Options { /** - * Maximum number of tags to attach to the asset. + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. */ - maxTags: number; + add_shadow?: boolean; /** - * Minimum confidence level for tags to be considered valid. + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. */ - minConfidence: number; + bg_color?: string; /** - * Specifies the auto-tagging extension used. - */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } - - export interface AIAutoDescription { - /** - * Specifies the auto description extension. - */ - name: 'ai-auto-description'; - } - - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { - /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs - >; + bg_image_url?: string; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. */ - pre?: string; - } - - export namespace Transformation { - export interface Transformation { - /** - * Transformation type. - */ - type: 'transformation'; - - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; - } - - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; - - /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` - */ - value?: string; - } - - export interface Thumbnail { - /** - * Generates a thumbnail image. - */ - type: 'thumbnail'; - - /** - * Optional transformation string. - * **Example**: `w-150,h-150` - */ - value?: string; - } - - export interface Abs { - /** - * Streaming protocol to use (`hls` or `dash`). - */ - protocol: 'hls' | 'dash'; - - /** - * Adaptive Bitrate Streaming (ABS) setup. - */ - type: 'abs'; - - /** - * List of different representations you want to create separated by an underscore. - */ - value: string; - } + semitransparency?: boolean; } } - export interface FileUploadByUrlv1 { - /** - * The URL of the file to upload. A publicly reachable URL that ImageKit servers - * can fetch. The server must receive the response headers within 8 seconds; - * otherwise the request fails with 400 Bad Request. - */ - file: string; - - /** - * The name with which the file has to be uploaded. The file name can contain: - * - * - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`. - * - Special Characters: `.`, `-` - * - * Any other character including space will be replaced by `_` - */ - fileName: string; - + export interface AutoTaggingExtension { /** - * A unique value that the ImageKit.io server will use to recognize and prevent - * subsequent retries for the same request. We suggest using V4 UUIDs, or another - * random string with enough entropy to avoid collisions. This field is only - * required for authentication when uploading a file from the client side. - * - * **Note**: Sending a value that has been used in the past will result in a - * validation error. Even if your previous request resulted in an error, you should - * always send a new value for this field. + * Maximum number of tags to attach to the asset. */ - token?: string; + maxTags: number; /** - * Server-side checks to run on the asset. Read more about - * [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks). + * Minimum confidence level for tags to be considered valid. */ - checks?: string; + minConfidence: number; /** - * Define an important area in the image. This is only relevant for image type - * files. - * - * - To be passed as a string with the x and y coordinates of the top-left corner, - * and width and height of the area of interest in the format `x,y,width,height`. - * For example - `10,10,100,100` - * - Can be used with fo-customtransformation. - * - If this field is not specified and the file is overwritten, then - * customCoordinates will be removed. + * Specifies the auto-tagging extension used. */ - customCoordinates?: string; - - /** - * JSON key-value pairs to associate with the asset. Create the custom metadata - * fields before setting these values. - */ - customMetadata?: { [key: string]: unknown }; - - /** - * Optional text to describe the contents of the file. - */ - description?: string; - - /** - * The time until your signature is valid. It must be a - * [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into - * the future. It should be in seconds. This field is only required for - * authentication when uploading a file from the client side. - */ - expire?: number; - - /** - * Array of extensions to be applied to the image. Each extension can be configured - * with specific parameters based on the extension type. - */ - extensions?: Array< - | FileUploadByUrlv1.RemoveBg - | FileUploadByUrlv1.AutoTaggingExtension - | FileUploadByUrlv1.AIAutoDescription - >; - - /** - * The folder path in which the image has to be uploaded. If the folder(s) didn't - * exist before, a new folder(s) is created. - * - * The folder name can contain: - * - * - Alphanumeric Characters: `a-z` , `A-Z` , `0-9` - * - Special Characters: `/` , `_` , `-` - * - * Using multiple `/` creates a nested folder. - */ - folder?: string; - - /** - * Whether to mark the file as private or not. - * - * If `true`, the file is marked as private and is accessible only using named - * transformation or signed URL. - */ - isPrivateFile?: boolean; - - /** - * Whether to upload file as published or not. - * - * If `false`, the file is marked as unpublished, which restricts access to the - * file only via the media library. Files in draft or unpublished state can only be - * publicly accessed after being published. - * - * The option to upload in draft state is only available in custom enterprise - * pricing plans. - */ - isPublished?: boolean; - - /** - * If set to `true` and a file already exists at the exact location, its AITags - * will be removed. Set `overwriteAITags` to `false` to preserve AITags. - */ - overwriteAITags?: boolean; - - /** - * If the request does not have `customMetadata`, and a file already exists at the - * exact location, existing customMetadata will be removed. - */ - overwriteCustomMetadata?: boolean; - - /** - * If `false` and `useUniqueFileName` is also `false`, and a file already exists at - * the exact location, upload API will return an error immediately. - */ - overwriteFile?: boolean; - - /** - * If the request does not have `tags`, and a file already exists at the exact - * location, existing tags will be removed. - */ - overwriteTags?: boolean; + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + export interface AIAutoDescription { /** - * Your ImageKit.io public key. This field is only required for authentication when - * uploading a file from the client side. + * Specifies the auto description extension. */ - publicKey?: string; + name: 'ai-auto-description'; + } + /** + * Configure pre-processing (`pre`) and post-processing (`post`) transformations. + * + * - `pre` — applied before the file is uploaded to the Media Library. + * Useful for reducing file size or applying basic optimizations upfront (e.g., + * resize, compress). + * + * - `post` — applied immediately after upload. + * Ideal for generating transformed versions (like video encodes or thumbnails) + * in advance, so they're ready for delivery without delay. + * + * You can mix and match any combination of post-processing types. + */ + export interface Transformation { /** - * Array of response field keys to include in the API response body. + * List of transformations to apply _after_ the file is uploaded. + * Each item must match one of the following types: `transformation`, + * `gif-to-video`, `thumbnail`, `abs`. */ - responseFields?: Array< - | 'tags' - | 'customCoordinates' - | 'isPrivateFile' - | 'embeddedMetadata' - | 'isPublished' - | 'customMetadata' - | 'metadata' + post?: Array< + | Transformation.Transformation + | Transformation.GifToVideo + | Transformation.Thumbnail + | Transformation.Abs >; /** - * HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a - * key. Learn how to create a signature on the page below. This should be in - * lowercase. - * - * Signature must be calculated on the server-side. This field is only required for - * authentication when uploading a file from the client side. - */ - signature?: string; - - /** - * Set the tags while uploading the file. Provide an array of tag strings (e.g. - * `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not - * exceed 500, and the `%` character is not allowed. If this field is not specified - * and the file is overwritten, the existing tags will be removed. + * Transformation string to apply before uploading the file to the Media Library. + * Useful for optimizing files at ingestion. */ - tags?: Array; - - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - transformation?: FileUploadByUrlv1.Transformation; - - /** - * Whether to use a unique filename for this file or not. - * - * If `true`, ImageKit.io will add a unique suffix to the filename parameter to get - * a unique filename. - * - * If `false`, then the image is uploaded with the provided filename parameter, and - * any existing file with the same name is replaced. - */ - useUniqueFileName?: boolean; - - /** - * The final status of extensions after they have completed execution will be - * delivered to this endpoint as a POST request. - * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) - * about the webhook payload structure. - */ - webhookUrl?: string; + pre?: string; } - export namespace FileUploadByUrlv1 { - export interface RemoveBg { + export namespace Transformation { + export interface Transformation { /** - * Specifies the background removal extension. + * Transformation type. */ - name: 'remove-bg'; + type: 'transformation'; - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } + /** + * Transformation string (e.g. `w-200,h-200`). + * Same syntax as ImageKit URL-based transformations. + */ + value: string; } - export interface AutoTaggingExtension { + export interface GifToVideo { /** - * Maximum number of tags to attach to the asset. + * Converts an animated GIF into an MP4. */ - maxTags: number; + type: 'gif-to-video'; /** - * Minimum confidence level for tags to be considered valid. + * Optional transformation string to apply to the output video. + * **Example**: `q-80` */ - minConfidence: number; + value?: string; + } + export interface Thumbnail { /** - * Specifies the auto-tagging extension used. + * Generates a thumbnail image. */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } + type: 'thumbnail'; - export interface AIAutoDescription { /** - * Specifies the auto description extension. + * Optional transformation string. + * **Example**: `w-150,h-150` */ - name: 'ai-auto-description'; + value?: string; } - /** - * Configure pre-processing (`pre`) and post-processing (`post`) transformations. - * - * - `pre` — applied before the file is uploaded to the Media Library. - * Useful for reducing file size or applying basic optimizations upfront (e.g., - * resize, compress). - * - * - `post` — applied immediately after upload. - * Ideal for generating transformed versions (like video encodes or thumbnails) - * in advance, so they're ready for delivery without delay. - * - * You can mix and match any combination of post-processing types. - */ - export interface Transformation { + export interface Abs { /** - * List of transformations to apply _after_ the file is uploaded. - * Each item must match one of the following types: `transformation`, - * `gif-to-video`, `thumbnail`, `abs`. + * Streaming protocol to use (`hls` or `dash`). */ - post?: Array< - | Transformation.Transformation - | Transformation.GifToVideo - | Transformation.Thumbnail - | Transformation.Abs - >; + protocol: 'hls' | 'dash'; /** - * Transformation string to apply before uploading the file to the Media Library. - * Useful for optimizing files at ingestion. + * Adaptive Bitrate Streaming (ABS) setup. */ - pre?: string; - } - - export namespace Transformation { - export interface Transformation { - /** - * Transformation type. - */ - type: 'transformation'; - - /** - * Transformation string (e.g. `w-200,h-200`). - * Same syntax as ImageKit URL-based transformations. - */ - value: string; - } + type: 'abs'; - export interface GifToVideo { - /** - * Converts an animated GIF into an MP4. - */ - type: 'gif-to-video'; - - /** - * Optional transformation string to apply to the output video. - * **Example**: `q-80` - */ - value?: string; - } - - export interface Thumbnail { - /** - * Generates a thumbnail image. - */ - type: 'thumbnail'; - - /** - * Optional transformation string. - * **Example**: `w-150,h-150` - */ - value?: string; - } - - export interface Abs { - /** - * Streaming protocol to use (`hls` or `dash`). - */ - protocol: 'hls' | 'dash'; - - /** - * Adaptive Bitrate Streaming (ABS) setup. - */ - type: 'abs'; - - /** - * List of different representations you want to create separated by an underscore. - */ - value: string; - } + /** + * List of different representations you want to create separated by an underscore. + */ + value: string; } } } From 01bdaa02fe0d6a5d5fcdca09edd31d4562031ca7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 05:59:46 +0000 Subject: [PATCH 47/73] feat(api): manual updates --- .stats.yml | 4 ++-- src/resources/beta/v2/files.ts | 2 +- src/resources/files/files.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index cfcbd7f7..5908457a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-667f7f4988b44bc587d6eb9960ff5c8326e9f7e9b072f3f724f9f54166eff8b1.yml -openapi_spec_hash: f2081864a4abee0480e5ff991b4c936a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0470196862abd722b09f1af798d6f2bcbdeba0f82d1162f57c287b1a43233531.yml +openapi_spec_hash: 043dd7c67d741d0034b86f2fc0bce072 config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts index bda12c3e..51e58e48 100644 --- a/src/resources/beta/v2/files.ts +++ b/src/resources/beta/v2/files.ts @@ -335,7 +335,7 @@ export interface FileUploadParams { description?: string; /** - * Array of extensions to be applied to the image. Each extension can be configured + * Array of extensions to be applied to the asset. Each extension can be configured * with specific parameters based on the extension type. */ extensions?: Array< diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 940ce2b6..b9cfe798 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1145,7 +1145,7 @@ export interface FileUploadParams { expire?: number; /** - * Array of extensions to be applied to the image. Each extension can be configured + * Array of extensions to be applied to the asset. Each extension can be configured * with specific parameters based on the extension type. */ extensions?: Array< From 76f3ed799e69105013d849c0d94de6778ab4da7a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:02:56 +0000 Subject: [PATCH 48/73] feat(api): manual updates --- .stats.yml | 2 +- api.md | 1 + src/client.ts | 1 + src/resources/beta/v2/files.ts | 67 +--------------- src/resources/files/files.ts | 137 +-------------------------------- src/resources/shared.ts | 72 +++++++++++++++++ 6 files changed, 80 insertions(+), 200 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5908457a..2dc65d21 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0470196862abd722b09f1af798d6f2bcbdeba0f82d1162f57c287b1a43233531.yml openapi_spec_hash: 043dd7c67d741d0034b86f2fc0bce072 -config_hash: 70f9408b8d1dfbcf262a20d6eed50e1c +config_hash: da949a1217f48ac01676eab81ca9d1b1 diff --git a/api.md b/api.md index 8e3ad709..7f73cc9b 100644 --- a/api.md +++ b/api.md @@ -3,6 +3,7 @@ Types: - BaseOverlay +- Extensions - ImageOverlay - Overlay - OverlayPosition diff --git a/src/client.ts b/src/client.ts index 0e650a0d..232c415d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -909,6 +909,7 @@ export declare namespace ImageKit { }; export type BaseOverlay = API.BaseOverlay; + export type Extensions = API.Extensions; export type ImageOverlay = API.ImageOverlay; export type Overlay = API.Overlay; export type OverlayPosition = API.OverlayPosition; diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts index 51e58e48..60cc172e 100644 --- a/src/resources/beta/v2/files.ts +++ b/src/resources/beta/v2/files.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../../core/resource'; +import * as Shared from '../../shared'; import * as FilesAPI from '../../files/files'; import { APIPromise } from '../../../core/api-promise'; import { type Uploadable } from '../../../core/uploads'; @@ -338,9 +339,7 @@ export interface FileUploadParams { * Array of extensions to be applied to the asset. Each extension can be configured * with specific parameters based on the extension type. */ - extensions?: Array< - FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription - >; + extensions?: Shared.Extensions; /** * The folder path in which the image has to be uploaded. If the folder(s) didn't @@ -450,68 +449,6 @@ export interface FileUploadParams { } export namespace FileUploadParams { - export interface RemoveBg { - /** - * Specifies the background removal extension. - */ - name: 'remove-bg'; - - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } - } - - export interface AutoTaggingExtension { - /** - * Maximum number of tags to attach to the asset. - */ - maxTags: number; - - /** - * Minimum confidence level for tags to be considered valid. - */ - minConfidence: number; - - /** - * Specifies the auto-tagging extension used. - */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } - - export interface AIAutoDescription { - /** - * Specifies the auto description extension. - */ - name: 'ai-auto-description'; - } - /** * Configure pre-processing (`pre`) and post-processing (`post`) transformations. * diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index b9cfe798..844ed716 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../core/resource'; +import * as Shared from '../shared'; import * as BulkAPI from './bulk'; import { Bulk, @@ -879,11 +880,7 @@ export namespace FileUpdateParams { * Array of extensions to be applied to the asset. Each extension can be configured * with specific parameters based on the extension type. */ - extensions?: Array< - | UpdateFileDetails.RemoveBg - | UpdateFileDetails.AutoTaggingExtension - | UpdateFileDetails.AIAutoDescription - >; + extensions?: Shared.Extensions; /** * An array of AITags associated with the file that you want to remove, e.g. @@ -912,70 +909,6 @@ export namespace FileUpdateParams { webhookUrl?: string; } - export namespace UpdateFileDetails { - export interface RemoveBg { - /** - * Specifies the background removal extension. - */ - name: 'remove-bg'; - - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } - } - - export interface AutoTaggingExtension { - /** - * Maximum number of tags to attach to the asset. - */ - maxTags: number; - - /** - * Minimum confidence level for tags to be considered valid. - */ - minConfidence: number; - - /** - * Specifies the auto-tagging extension used. - */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } - - export interface AIAutoDescription { - /** - * Specifies the auto description extension. - */ - name: 'ai-auto-description'; - } - } - export interface ChangePublicationStatus { /** * Configure the publication status of a file and its versions. @@ -1148,9 +1081,7 @@ export interface FileUploadParams { * Array of extensions to be applied to the asset. Each extension can be configured * with specific parameters based on the extension type. */ - extensions?: Array< - FileUploadParams.RemoveBg | FileUploadParams.AutoTaggingExtension | FileUploadParams.AIAutoDescription - >; + extensions?: Shared.Extensions; /** * The folder path in which the image has to be uploaded. If the folder(s) didn't @@ -1282,68 +1213,6 @@ export interface FileUploadParams { } export namespace FileUploadParams { - export interface RemoveBg { - /** - * Specifies the background removal extension. - */ - name: 'remove-bg'; - - options?: RemoveBg.Options; - } - - export namespace RemoveBg { - export interface Options { - /** - * Whether to add an artificial shadow to the result. Default is false. Note: - * Adding shadows is currently only supported for car photos. - */ - add_shadow?: boolean; - - /** - * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or - * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be - * empty. - */ - bg_color?: string; - - /** - * Sets a background image from a URL. If this parameter is set, `bg_color` must be - * empty. - */ - bg_image_url?: string; - - /** - * Allows semi-transparent regions in the result. Default is true. Note: - * Semitransparency is currently only supported for car windows. - */ - semitransparency?: boolean; - } - } - - export interface AutoTaggingExtension { - /** - * Maximum number of tags to attach to the asset. - */ - maxTags: number; - - /** - * Minimum confidence level for tags to be considered valid. - */ - minConfidence: number; - - /** - * Specifies the auto-tagging extension used. - */ - name: 'google-auto-tagging' | 'aws-auto-tagging'; - } - - export interface AIAutoDescription { - /** - * Specifies the auto description extension. - */ - name: 'ai-auto-description'; - } - /** * Configure pre-processing (`pre`) and post-processing (`post`) transformations. * diff --git a/src/resources/shared.ts b/src/resources/shared.ts index f50d7390..6d73d695 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -6,6 +6,78 @@ export interface BaseOverlay { timing?: OverlayTiming; } +/** + * Array of extensions to be applied to the asset. Each extension can be configured + * with specific parameters based on the extension type. + */ +export type Extensions = Array< + Extensions.RemoveBg | Extensions.AutoTaggingExtension | Extensions.AIAutoDescription +>; + +export namespace Extensions { + export interface RemoveBg { + /** + * Specifies the background removal extension. + */ + name: 'remove-bg'; + + options?: RemoveBg.Options; + } + + export namespace RemoveBg { + export interface Options { + /** + * Whether to add an artificial shadow to the result. Default is false. Note: + * Adding shadows is currently only supported for car photos. + */ + add_shadow?: boolean; + + /** + * Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or + * color name (e.g., "green"). If this parameter is set, `bg_image_url` must be + * empty. + */ + bg_color?: string; + + /** + * Sets a background image from a URL. If this parameter is set, `bg_color` must be + * empty. + */ + bg_image_url?: string; + + /** + * Allows semi-transparent regions in the result. Default is true. Note: + * Semitransparency is currently only supported for car windows. + */ + semitransparency?: boolean; + } + } + + export interface AutoTaggingExtension { + /** + * Maximum number of tags to attach to the asset. + */ + maxTags: number; + + /** + * Minimum confidence level for tags to be considered valid. + */ + minConfidence: number; + + /** + * Specifies the auto-tagging extension used. + */ + name: 'google-auto-tagging' | 'aws-auto-tagging'; + } + + export interface AIAutoDescription { + /** + * Specifies the auto description extension. + */ + name: 'ai-auto-description'; + } +} + export interface ImageOverlay extends BaseOverlay { /** * Specifies the relative path to the image used as an overlay. From d208673da821f783d8279d6eb22bfe1e41ee4f62 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:46:55 +0000 Subject: [PATCH 49/73] feat(api): manual updates --- .stats.yml | 2 +- src/resources/accounts/origins.ts | 610 ++++++++++++++++++- src/resources/files/files.ts | 34 +- tests/api-resources/accounts/origins.test.ts | 60 +- tests/api-resources/files/files.test.ts | 39 +- 5 files changed, 651 insertions(+), 94 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2dc65d21..335e38ec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0470196862abd722b09f1af798d6f2bcbdeba0f82d1162f57c287b1a43233531.yml openapi_spec_hash: 043dd7c67d741d0034b86f2fc0bce072 -config_hash: da949a1217f48ac01676eab81ca9d1b1 +config_hash: a652d68098d82eaf611a49507fb4b831 diff --git a/src/resources/accounts/origins.ts b/src/resources/accounts/origins.ts index 39730cfe..f59a468e 100644 --- a/src/resources/accounts/origins.ts +++ b/src/resources/accounts/origins.ts @@ -15,20 +15,17 @@ export class Origins extends APIResource { * ```ts * const originResponse = await client.accounts.origins.create( * { - * origin: { - * accessKey: 'AKIATEST123', - * bucket: 'test-bucket', - * name: 'My S3 Origin', - * secretKey: 'secrettest123', - * type: 'S3', - * }, + * accessKey: 'AKIAIOSFODNN7EXAMPLE', + * bucket: 'product-images', + * name: 'US S3 Storage', + * secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + * type: 'S3', * }, * ); * ``` */ - create(params: OriginCreateParams, options?: RequestOptions): APIPromise { - const { origin } = params; - return this._client.post('/v1/accounts/origins', { body: origin, ...options }); + create(body: OriginCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/accounts/origins', { body, ...options }); } /** @@ -40,20 +37,17 @@ export class Origins extends APIResource { * const originResponse = await client.accounts.origins.update( * 'id', * { - * origin: { - * accessKey: 'AKIATEST123', - * bucket: 'test-bucket', - * name: 'My S3 Origin', - * secretKey: 'secrettest123', - * type: 'S3', - * }, + * accessKey: 'AKIAIOSFODNN7EXAMPLE', + * bucket: 'product-images', + * name: 'US S3 Storage', + * secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + * type: 'S3', * }, * ); * ``` */ - update(id: string, params: OriginUpdateParams, options?: RequestOptions): APIPromise { - const { origin } = params; - return this._client.put(path`/v1/accounts/origins/${id}`, { body: origin, ...options }); + update(id: string, body: OriginUpdateParams, options?: RequestOptions): APIPromise { + return this._client.put(path`/v1/accounts/origins/${id}`, { body, ...options }); } /** @@ -675,18 +669,574 @@ export namespace OriginResponse { export type OriginListResponse = Array; -export interface OriginCreateParams { - /** - * Schema for origin request resources. - */ - origin: OriginRequest; +export type OriginCreateParams = + | OriginCreateParams.S3 + | OriginCreateParams.S3Compatible + | OriginCreateParams.CloudinaryBackup + | OriginCreateParams.WebFolder + | OriginCreateParams.WebProxy + | OriginCreateParams.GoogleCloudStorageGcs + | OriginCreateParams.AzureBlobStorage + | OriginCreateParams.AkeneoPim; + +export declare namespace OriginCreateParams { + export interface S3 { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface S3Compatible { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Custom S3-compatible endpoint. + */ + endpoint: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3_COMPATIBLE'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + + /** + * Use path-style S3 URLs? + */ + s3ForcePathStyle?: boolean; + } + + export interface CloudinaryBackup { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'CLOUDINARY_BACKUP'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface WebFolder { + /** + * Root URL for the web folder origin. + */ + baseUrl: string; + + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_FOLDER'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Forward the Host header to origin? + */ + forwardHostHeaderToOrigin?: boolean; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface WebProxy { + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_PROXY'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface GoogleCloudStorageGcs { + bucket: string; + + clientEmail: string; + + /** + * Display name of the origin. + */ + name: string; + + privateKey: string; + + type: 'GCS'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AzureBlobStorage { + accountName: string; + + container: string; + + /** + * Display name of the origin. + */ + name: string; + + sasToken: string; + + type: 'AZURE_BLOB'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AkeneoPim { + /** + * Akeneo instance base URL. + */ + baseUrl: string; + + /** + * Akeneo API client ID. + */ + clientId: string; + + /** + * Akeneo API client secret. + */ + clientSecret: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Akeneo API password. + */ + password: string; + + type: 'AKENEO_PIM'; + + /** + * Akeneo API username. + */ + username: string; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } } -export interface OriginUpdateParams { - /** - * Schema for origin request resources. - */ - origin: OriginRequest; +export type OriginUpdateParams = + | OriginUpdateParams.S3 + | OriginUpdateParams.S3Compatible + | OriginUpdateParams.CloudinaryBackup + | OriginUpdateParams.WebFolder + | OriginUpdateParams.WebProxy + | OriginUpdateParams.GoogleCloudStorageGcs + | OriginUpdateParams.AzureBlobStorage + | OriginUpdateParams.AkeneoPim; + +export declare namespace OriginUpdateParams { + export interface S3 { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface S3Compatible { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Custom S3-compatible endpoint. + */ + endpoint: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'S3_COMPATIBLE'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + + /** + * Use path-style S3 URLs? + */ + s3ForcePathStyle?: boolean; + } + + export interface CloudinaryBackup { + /** + * Access key for the bucket. + */ + accessKey: string; + + /** + * S3 bucket name. + */ + bucket: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Secret key for the bucket. + */ + secretKey: string; + + type: 'CLOUDINARY_BACKUP'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + /** + * Path prefix inside the bucket. + */ + prefix?: string; + } + + export interface WebFolder { + /** + * Root URL for the web folder origin. + */ + baseUrl: string; + + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_FOLDER'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Forward the Host header to origin? + */ + forwardHostHeaderToOrigin?: boolean; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface WebProxy { + /** + * Display name of the origin. + */ + name: string; + + type: 'WEB_PROXY'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } + + export interface GoogleCloudStorageGcs { + bucket: string; + + clientEmail: string; + + /** + * Display name of the origin. + */ + name: string; + + privateKey: string; + + type: 'GCS'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AzureBlobStorage { + accountName: string; + + container: string; + + /** + * Display name of the origin. + */ + name: string; + + sasToken: string; + + type: 'AZURE_BLOB'; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + + prefix?: string; + } + + export interface AkeneoPim { + /** + * Akeneo instance base URL. + */ + baseUrl: string; + + /** + * Akeneo API client ID. + */ + clientId: string; + + /** + * Akeneo API client secret. + */ + clientSecret: string; + + /** + * Display name of the origin. + */ + name: string; + + /** + * Akeneo API password. + */ + password: string; + + type: 'AKENEO_PIM'; + + /** + * Akeneo API username. + */ + username: string; + + /** + * URL used in the Canonical header (if enabled). + */ + baseUrlForCanonicalHeader?: string; + + /** + * Whether to send a Canonical header. + */ + includeCanonicalHeader?: boolean; + } } export declare namespace Origins { diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 844ed716..057a5fdf 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -45,16 +45,36 @@ export class Files extends APIResource { * * @example * ```ts - * const file = await client.files.update('fileId'); + * const file = await client.files.update('fileId', { + * customCoordinates: '10,10,100,100', + * customMetadata: { brand: 'Nike', color: 'red' }, + * extensions: [ + * { name: 'remove-bg', options: { add_shadow: true } }, + * { + * name: 'google-auto-tagging', + * minConfidence: 80, + * maxTags: 10, + * }, + * { + * name: 'aws-auto-tagging', + * minConfidence: 80, + * maxTags: 10, + * }, + * { name: 'ai-auto-description' }, + * ], + * removeAITags: ['car', 'vehicle', 'motorsports'], + * tags: ['tag1', 'tag2'], + * webhookUrl: + * 'https://webhook.site/0d6b6c7a-8e5a-4b3a-8b7c-0d6b6c7a8e5a', + * }); * ``` */ update( fileID: string, - params: FileUpdateParams | null | undefined = undefined, + body: FileUpdateParams | null | undefined = {}, options?: RequestOptions, ): APIPromise { - const { update } = params ?? {}; - return this._client.patch(path`/v1/files/${fileID}/details`, { body: update, ...options }); + return this._client.patch(path`/v1/files/${fileID}/details`, { body, ...options }); } /** @@ -852,11 +872,9 @@ export namespace FileUploadResponse { } } -export interface FileUpdateParams { - update?: FileUpdateParams.UpdateFileDetails | FileUpdateParams.ChangePublicationStatus; -} +export type FileUpdateParams = FileUpdateParams.UpdateFileDetails | FileUpdateParams.ChangePublicationStatus; -export namespace FileUpdateParams { +export declare namespace FileUpdateParams { export interface UpdateFileDetails { /** * Define an important area in the image in the format `x,y,width,height` e.g. diff --git a/tests/api-resources/accounts/origins.test.ts b/tests/api-resources/accounts/origins.test.ts index 6881e301..4aee2b5d 100644 --- a/tests/api-resources/accounts/origins.test.ts +++ b/tests/api-resources/accounts/origins.test.ts @@ -12,13 +12,11 @@ describe('resource origins', () => { // Prism tests are disabled test.skip('create: only required params', async () => { const responsePromise = client.accounts.origins.create({ - origin: { - accessKey: 'AKIATEST123', - bucket: 'test-bucket', - name: 'My S3 Origin', - secretKey: 'secrettest123', - type: 'S3', - }, + accessKey: 'AKIAIOSFODNN7EXAMPLE', + bucket: 'product-images', + name: 'US S3 Storage', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + type: 'S3', }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -32,29 +30,25 @@ describe('resource origins', () => { // Prism tests are disabled test.skip('create: required and optional params', async () => { const response = await client.accounts.origins.create({ - origin: { - accessKey: 'AKIATEST123', - bucket: 'test-bucket', - name: 'My S3 Origin', - secretKey: 'secrettest123', - type: 'S3', - baseUrlForCanonicalHeader: 'https://cdn.example.com', - includeCanonicalHeader: false, - prefix: 'images', - }, + accessKey: 'AKIAIOSFODNN7EXAMPLE', + bucket: 'product-images', + name: 'US S3 Storage', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + type: 'S3', + baseUrlForCanonicalHeader: 'https://cdn.example.com', + includeCanonicalHeader: false, + prefix: 'raw-assets', }); }); // Prism tests are disabled test.skip('update: only required params', async () => { const responsePromise = client.accounts.origins.update('id', { - origin: { - accessKey: 'AKIATEST123', - bucket: 'test-bucket', - name: 'My S3 Origin', - secretKey: 'secrettest123', - type: 'S3', - }, + accessKey: 'AKIAIOSFODNN7EXAMPLE', + bucket: 'product-images', + name: 'US S3 Storage', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + type: 'S3', }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -68,16 +62,14 @@ describe('resource origins', () => { // Prism tests are disabled test.skip('update: required and optional params', async () => { const response = await client.accounts.origins.update('id', { - origin: { - accessKey: 'AKIATEST123', - bucket: 'test-bucket', - name: 'My S3 Origin', - secretKey: 'secrettest123', - type: 'S3', - baseUrlForCanonicalHeader: 'https://cdn.example.com', - includeCanonicalHeader: false, - prefix: 'images', - }, + accessKey: 'AKIAIOSFODNN7EXAMPLE', + bucket: 'product-images', + name: 'US S3 Storage', + secretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + type: 'S3', + baseUrlForCanonicalHeader: 'https://cdn.example.com', + includeCanonicalHeader: false, + prefix: 'raw-assets', }); }); diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index 8969d90b..a11ad627 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -28,28 +28,25 @@ describe('resource files', () => { client.files.update( 'fileId', { - update: { - customCoordinates: '10,10,100,100', - customMetadata: { brand: 'bar', color: 'bar' }, - description: 'description', - extensions: [ - { - name: 'remove-bg', - options: { - add_shadow: true, - bg_color: 'bg_color', - bg_image_url: 'bg_image_url', - semitransparency: true, - }, + customCoordinates: 'customCoordinates', + customMetadata: { foo: 'bar' }, + description: 'description', + extensions: [ + { + name: 'remove-bg', + options: { + add_shadow: true, + bg_color: 'bg_color', + bg_image_url: 'bg_image_url', + semitransparency: true, }, - { maxTags: 10, minConfidence: 80, name: 'google-auto-tagging' }, - { maxTags: 10, minConfidence: 80, name: 'aws-auto-tagging' }, - { name: 'ai-auto-description' }, - ], - removeAITags: ['car', 'vehicle', 'motorsports'], - tags: ['tag1', 'tag2'], - webhookUrl: 'https://webhook.site/0d6b6c7a-8e5a-4b3a-8b7c-0d6b6c7a8e5a', - }, + }, + { maxTags: 5, minConfidence: 95, name: 'google-auto-tagging' }, + { name: 'ai-auto-description' }, + ], + removeAITags: ['string'], + tags: ['tag1', 'tag2'], + webhookUrl: 'https://example.com', }, { path: '/_stainless_unknown_path' }, ), From e865520554d35c07fe4bcc207a21e4f3fa892eb7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 07:05:09 +0000 Subject: [PATCH 50/73] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 335e38ec..e13d3275 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-0470196862abd722b09f1af798d6f2bcbdeba0f82d1162f57c287b1a43233531.yml -openapi_spec_hash: 043dd7c67d741d0034b86f2fc0bce072 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-afb67c3a2098b1a2dca37d0995d183fa6cf59dd144ed47bcdb924f14e3cc90a3.yml +openapi_spec_hash: 55a2f38df85126a87d1be0b27b9b8470 config_hash: a652d68098d82eaf611a49507fb4b831 From c6a7e04c0bc15659b6968d379845ffefd2fa0879 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 07:10:54 +0000 Subject: [PATCH 51/73] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e13d3275..bbfdcb43 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-afb67c3a2098b1a2dca37d0995d183fa6cf59dd144ed47bcdb924f14e3cc90a3.yml -openapi_spec_hash: 55a2f38df85126a87d1be0b27b9b8470 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-bc7c0d27962b30c19c778656988e154b54696819389289f34420a5e5fdfbd3b8.yml +openapi_spec_hash: 1bfde02a63416c036e9545927f727459 config_hash: a652d68098d82eaf611a49507fb4b831 From 30d976b95ae76c09bc9152badd6bed801bf3cf57 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:07:28 +0000 Subject: [PATCH 52/73] feat(api): extract UpdateFileDetailsRequest to model --- .stats.yml | 2 +- api.md | 1 + src/client.ts | 2 + src/resources/files/files.ts | 84 ++++++++++++++++++++++++++++++++++++ src/resources/files/index.ts | 1 + src/resources/index.ts | 1 + 6 files changed, 90 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index bbfdcb43..b0086e96 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-bc7c0d27962b30c19c778656988e154b54696819389289f34420a5e5fdfbd3b8.yml openapi_spec_hash: 1bfde02a63416c036e9545927f727459 -config_hash: a652d68098d82eaf611a49507fb4b831 +config_hash: b415c06a3b29485af4601beb94ae1aeb diff --git a/api.md b/api.md index 7f73cc9b..78b8a89c 100644 --- a/api.md +++ b/api.md @@ -42,6 +42,7 @@ Types: - File - Folder - Metadata +- UpdateFileDetailsRequest - FileUpdateResponse - FileCopyResponse - FileMoveResponse diff --git a/src/client.ts b/src/client.ts index 232c415d..6a110c35 100644 --- a/src/client.ts +++ b/src/client.ts @@ -57,6 +57,7 @@ import { Files, Folder, Metadata, + UpdateFileDetailsRequest, } from './resources/files/files'; import { FolderCopyParams, @@ -856,6 +857,7 @@ export declare namespace ImageKit { type File as File, type Folder as Folder, type Metadata as Metadata, + type UpdateFileDetailsRequest as UpdateFileDetailsRequest, type FileUpdateResponse as FileUpdateResponse, type FileCopyResponse as FileCopyResponse, type FileMoveResponse as FileMoveResponse, diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 057a5fdf..a2007b6e 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -632,6 +632,89 @@ export namespace Metadata { } } +export type UpdateFileDetailsRequest = + | UpdateFileDetailsRequest.UpdateFileDetails + | UpdateFileDetailsRequest.ChangePublicationStatus; + +export namespace UpdateFileDetailsRequest { + export interface UpdateFileDetails { + /** + * Define an important area in the image in the format `x,y,width,height` e.g. + * `10,10,100,100`. Send `null` to unset this value. + */ + customCoordinates?: string | null; + + /** + * A key-value data to be associated with the asset. To unset a key, send `null` + * value for that key. Before setting any custom metadata on an asset you have to + * create the field using custom metadata fields API. + */ + customMetadata?: { [key: string]: unknown }; + + /** + * Optional text to describe the contents of the file. + */ + description?: string; + + /** + * Array of extensions to be applied to the asset. Each extension can be configured + * with specific parameters based on the extension type. + */ + extensions?: Shared.Extensions; + + /** + * An array of AITags associated with the file that you want to remove, e.g. + * `["car", "vehicle", "motorsports"]`. + * + * If you want to remove all AITags associated with the file, send a string - + * "all". + * + * Note: The remove operation for `AITags` executes before any of the `extensions` + * are processed. + */ + removeAITags?: Array | 'all'; + + /** + * An array of tags associated with the file, such as `["tag1", "tag2"]`. Send + * `null` to unset all tags associated with the file. + */ + tags?: Array | null; + + /** + * The final status of extensions after they have completed execution will be + * delivered to this endpoint as a POST request. + * [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) + * about the webhook payload structure. + */ + webhookUrl?: string; + } + + export interface ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + publish?: ChangePublicationStatus.Publish; + } + + export namespace ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + export interface Publish { + /** + * Set to `true` to publish the file. Set to `false` to unpublish the file. + */ + isPublished: boolean; + + /** + * Set to `true` to publish/unpublish all versions of the file. Set to `false` to + * publish/unpublish only the current version of the file. + */ + includeFileVersions?: boolean; + } + } +} + /** * Object containing details of a file or file version. */ @@ -1331,6 +1414,7 @@ export declare namespace Files { type File as File, type Folder as Folder, type Metadata as Metadata, + type UpdateFileDetailsRequest as UpdateFileDetailsRequest, type FileUpdateResponse as FileUpdateResponse, type FileCopyResponse as FileCopyResponse, type FileMoveResponse as FileMoveResponse, diff --git a/src/resources/files/index.ts b/src/resources/files/index.ts index e194b456..0082557b 100644 --- a/src/resources/files/index.ts +++ b/src/resources/files/index.ts @@ -16,6 +16,7 @@ export { type File, type Folder, type Metadata, + type UpdateFileDetailsRequest, type FileUpdateResponse, type FileCopyResponse, type FileMoveResponse, diff --git a/src/resources/index.ts b/src/resources/index.ts index 39d0cc8b..de6db996 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -19,6 +19,7 @@ export { type File, type Folder, type Metadata, + type UpdateFileDetailsRequest, type FileUpdateResponse, type FileCopyResponse, type FileMoveResponse, From 06a988278c597a54f8d7e7b5c23d62cfae4079b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:57:16 +0000 Subject: [PATCH 53/73] chore: ci build action --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 1d893fde..0870aebc 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -12,7 +12,7 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz "${BUILD_PATH:-dist}" | curl -v -X PUT \ +UPLOAD_RESPONSE=$(tar "${BASE_PATH:+-C$BASE_PATH}" -cz "${ARTIFACT_PATH:-dist}" | curl -v -X PUT \ -H "Content-Type: application/gzip" \ --data-binary @- "$SIGNED_URL" 2>&1) From 66e3b81cc8d6a1a123c0622c08801ecbdeef4f9f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 02:21:50 +0000 Subject: [PATCH 54/73] fix: coerce nullable values to undefined --- src/internal/utils/values.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts index 532f2c31..d73aadd0 100644 --- a/src/internal/utils/values.ts +++ b/src/internal/utils/values.ts @@ -76,21 +76,21 @@ export const coerceBoolean = (value: unknown): boolean => { }; export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceInteger(value); }; export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceFloat(value); }; export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceBoolean(value); From 2ac76564b2119db0d9d4eddb399ddf422ecc6eba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:57:39 +0000 Subject: [PATCH 55/73] feat(api): manual updates --- .stats.yml | 6 +- api.md | 2 +- src/client.ts | 4 +- src/resources/files/files.ts | 89 +++---------------------- src/resources/files/index.ts | 2 +- src/resources/index.ts | 2 +- tests/api-resources/files/files.test.ts | 34 +--------- 7 files changed, 19 insertions(+), 120 deletions(-) diff --git a/.stats.yml b/.stats.yml index b0086e96..bb4a090c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-bc7c0d27962b30c19c778656988e154b54696819389289f34420a5e5fdfbd3b8.yml -openapi_spec_hash: 1bfde02a63416c036e9545927f727459 -config_hash: b415c06a3b29485af4601beb94ae1aeb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-dd864816d7f4316ae89f57394da2fd1926166d4704db5a0bb5d23461d2d75e49.yml +openapi_spec_hash: 7f7c416563a15bbaea98804ecdc1a8f9 +config_hash: 54c05a157f2cc730fac9e1df5dc3ca29 diff --git a/api.md b/api.md index 78b8a89c..86719ecf 100644 --- a/api.md +++ b/api.md @@ -42,7 +42,7 @@ Types: - File - Folder - Metadata -- UpdateFileDetailsRequest +- UpdateFileRequest - FileUpdateResponse - FileCopyResponse - FileMoveResponse diff --git a/src/client.ts b/src/client.ts index 6a110c35..8d83f1d7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -57,7 +57,7 @@ import { Files, Folder, Metadata, - UpdateFileDetailsRequest, + UpdateFileRequest, } from './resources/files/files'; import { FolderCopyParams, @@ -857,7 +857,7 @@ export declare namespace ImageKit { type File as File, type Folder as Folder, type Metadata as Metadata, - type UpdateFileDetailsRequest as UpdateFileDetailsRequest, + type UpdateFileRequest as UpdateFileRequest, type FileUpdateResponse as FileUpdateResponse, type FileCopyResponse as FileCopyResponse, type FileMoveResponse as FileMoveResponse, diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index a2007b6e..169ece36 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -45,35 +45,11 @@ export class Files extends APIResource { * * @example * ```ts - * const file = await client.files.update('fileId', { - * customCoordinates: '10,10,100,100', - * customMetadata: { brand: 'Nike', color: 'red' }, - * extensions: [ - * { name: 'remove-bg', options: { add_shadow: true } }, - * { - * name: 'google-auto-tagging', - * minConfidence: 80, - * maxTags: 10, - * }, - * { - * name: 'aws-auto-tagging', - * minConfidence: 80, - * maxTags: 10, - * }, - * { name: 'ai-auto-description' }, - * ], - * removeAITags: ['car', 'vehicle', 'motorsports'], - * tags: ['tag1', 'tag2'], - * webhookUrl: - * 'https://webhook.site/0d6b6c7a-8e5a-4b3a-8b7c-0d6b6c7a8e5a', - * }); + * const file = await client.files.update('fileId'); * ``` */ - update( - fileID: string, - body: FileUpdateParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { + update(fileID: string, params: FileUpdateParams, options?: RequestOptions): APIPromise { + const body = 'body' in params ? params.body : params; return this._client.patch(path`/v1/files/${fileID}/details`, { body, ...options }); } @@ -632,11 +608,12 @@ export namespace Metadata { } } -export type UpdateFileDetailsRequest = - | UpdateFileDetailsRequest.UpdateFileDetails - | UpdateFileDetailsRequest.ChangePublicationStatus; +/** + * Schema for update file update request. + */ +export type UpdateFileRequest = UpdateFileRequest.UpdateFileDetails | unknown; -export namespace UpdateFileDetailsRequest { +export namespace UpdateFileRequest { export interface UpdateFileDetails { /** * Define an important area in the image in the format `x,y,width,height` e.g. @@ -688,31 +665,6 @@ export namespace UpdateFileDetailsRequest { */ webhookUrl?: string; } - - export interface ChangePublicationStatus { - /** - * Configure the publication status of a file and its versions. - */ - publish?: ChangePublicationStatus.Publish; - } - - export namespace ChangePublicationStatus { - /** - * Configure the publication status of a file and its versions. - */ - export interface Publish { - /** - * Set to `true` to publish the file. Set to `false` to unpublish the file. - */ - isPublished: boolean; - - /** - * Set to `true` to publish/unpublish all versions of the file. Set to `false` to - * publish/unpublish only the current version of the file. - */ - includeFileVersions?: boolean; - } - } } /** @@ -1011,28 +963,7 @@ export declare namespace FileUpdateParams { } export interface ChangePublicationStatus { - /** - * Configure the publication status of a file and its versions. - */ - publish?: ChangePublicationStatus.Publish; - } - - export namespace ChangePublicationStatus { - /** - * Configure the publication status of a file and its versions. - */ - export interface Publish { - /** - * Set to `true` to publish the file. Set to `false` to unpublish the file. - */ - isPublished: boolean; - - /** - * Set to `true` to publish/unpublish all versions of the file. Set to `false` to - * publish/unpublish only the current version of the file. - */ - includeFileVersions?: boolean; - } + body: unknown; } } @@ -1414,7 +1345,7 @@ export declare namespace Files { type File as File, type Folder as Folder, type Metadata as Metadata, - type UpdateFileDetailsRequest as UpdateFileDetailsRequest, + type UpdateFileRequest as UpdateFileRequest, type FileUpdateResponse as FileUpdateResponse, type FileCopyResponse as FileCopyResponse, type FileMoveResponse as FileMoveResponse, diff --git a/src/resources/files/index.ts b/src/resources/files/index.ts index 0082557b..de6d1ad7 100644 --- a/src/resources/files/index.ts +++ b/src/resources/files/index.ts @@ -16,7 +16,7 @@ export { type File, type Folder, type Metadata, - type UpdateFileDetailsRequest, + type UpdateFileRequest, type FileUpdateResponse, type FileCopyResponse, type FileMoveResponse, diff --git a/src/resources/index.ts b/src/resources/index.ts index de6db996..610aa27d 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -19,7 +19,7 @@ export { type File, type Folder, type Metadata, - type UpdateFileDetailsRequest, + type UpdateFileRequest, type FileUpdateResponse, type FileCopyResponse, type FileMoveResponse, diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index a11ad627..709c6214 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -11,7 +11,7 @@ const client = new ImageKit({ describe('resource files', () => { // Prism tests are disabled test.skip('update', async () => { - const responsePromise = client.files.update('fileId'); + const responsePromise = client.files.update('fileId', {}); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -21,38 +21,6 @@ describe('resource files', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('update: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.files.update( - 'fileId', - { - customCoordinates: 'customCoordinates', - customMetadata: { foo: 'bar' }, - description: 'description', - extensions: [ - { - name: 'remove-bg', - options: { - add_shadow: true, - bg_color: 'bg_color', - bg_image_url: 'bg_image_url', - semitransparency: true, - }, - }, - { maxTags: 5, minConfidence: 95, name: 'google-auto-tagging' }, - { name: 'ai-auto-description' }, - ], - removeAITags: ['string'], - tags: ['tag1', 'tag2'], - webhookUrl: 'https://example.com', - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(ImageKit.NotFoundError); - }); - // Prism tests are disabled test.skip('delete', async () => { const responsePromise = client.files.delete('fileId'); From af5fd2f465f9d00d4822e1fac77cbe323094bd33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:02:13 +0000 Subject: [PATCH 56/73] feat(api): manual updates --- .stats.yml | 4 +-- src/resources/files/files.ts | 55 +++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index bb4a090c..c9c84db9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-dd864816d7f4316ae89f57394da2fd1926166d4704db5a0bb5d23461d2d75e49.yml -openapi_spec_hash: 7f7c416563a15bbaea98804ecdc1a8f9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml +openapi_spec_hash: 1d382866fce3284f26d341f112988d9d config_hash: 54c05a157f2cc730fac9e1df5dc3ca29 diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 169ece36..9093caba 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -48,8 +48,7 @@ export class Files extends APIResource { * const file = await client.files.update('fileId'); * ``` */ - update(fileID: string, params: FileUpdateParams, options?: RequestOptions): APIPromise { - const body = 'body' in params ? params.body : params; + update(fileID: string, body: FileUpdateParams, options?: RequestOptions): APIPromise { return this._client.patch(path`/v1/files/${fileID}/details`, { body, ...options }); } @@ -611,7 +610,9 @@ export namespace Metadata { /** * Schema for update file update request. */ -export type UpdateFileRequest = UpdateFileRequest.UpdateFileDetails | unknown; +export type UpdateFileRequest = + | UpdateFileRequest.UpdateFileDetails + | UpdateFileRequest.ChangePublicationStatus; export namespace UpdateFileRequest { export interface UpdateFileDetails { @@ -665,6 +666,31 @@ export namespace UpdateFileRequest { */ webhookUrl?: string; } + + export interface ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + publish?: ChangePublicationStatus.Publish; + } + + export namespace ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + export interface Publish { + /** + * Set to `true` to publish the file. Set to `false` to unpublish the file. + */ + isPublished: boolean; + + /** + * Set to `true` to publish/unpublish all versions of the file. Set to `false` to + * publish/unpublish only the current version of the file. + */ + includeFileVersions?: boolean; + } + } } /** @@ -963,7 +989,28 @@ export declare namespace FileUpdateParams { } export interface ChangePublicationStatus { - body: unknown; + /** + * Configure the publication status of a file and its versions. + */ + publish?: ChangePublicationStatus.Publish; + } + + export namespace ChangePublicationStatus { + /** + * Configure the publication status of a file and its versions. + */ + export interface Publish { + /** + * Set to `true` to publish the file. Set to `false` to unpublish the file. + */ + isPublished: boolean; + + /** + * Set to `true` to publish/unpublish all versions of the file. Set to `false` to + * publish/unpublish only the current version of the file. + */ + includeFileVersions?: boolean; + } } } From 78f9507314187a0a925eb095168caa5759b7d42b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:38:09 +0000 Subject: [PATCH 57/73] feat(api): manual updates --- .stats.yml | 2 +- README.md | 4 +- src/client.ts | 29 +++-- tests/api-resources/accounts/origins.test.ts | 2 +- .../accounts/url-endpoints.test.ts | 2 +- tests/api-resources/accounts/usage.test.ts | 2 +- tests/api-resources/assets.test.ts | 2 +- tests/api-resources/beta/v2/files.test.ts | 2 +- .../api-resources/cache/invalidation.test.ts | 2 +- .../custom-metadata-fields.test.ts | 2 +- tests/api-resources/files/bulk.test.ts | 2 +- tests/api-resources/files/files.test.ts | 2 +- tests/api-resources/files/metadata.test.ts | 2 +- tests/api-resources/files/versions.test.ts | 2 +- tests/api-resources/folders/folders.test.ts | 2 +- tests/api-resources/folders/job.test.ts | 2 +- tests/api-resources/webhooks.test.ts | 2 +- tests/index.test.ts | 106 +++++++----------- 18 files changed, 74 insertions(+), 95 deletions(-) diff --git a/.stats.yml b/.stats.yml index c9c84db9..00fd95da 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: 54c05a157f2cc730fac9e1df5dc3ca29 +config_hash: 29a2351fe2be89392b15719be8bc964f diff --git a/README.md b/README.md index 5f46d231..5f819a47 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The full API of this library can be found in [api.md](api.md). import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + privateKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); @@ -47,7 +47,7 @@ This library includes TypeScript definitions for all request params and response import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + privateKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); diff --git a/src/client.ts b/src/client.ts index 8d83f1d7..476df619 100644 --- a/src/client.ts +++ b/src/client.ts @@ -92,11 +92,10 @@ export interface ClientOptions { * You can view and manage API keys in the [dashboard](https://imagekit.io/dashboard/developer/api-keys). * */ - privateAPIKey?: string | undefined; + privateKey?: string | undefined; /** - * ImageKit Basic Auth only uses the username field and ignores the password. - * This field is unused. + * ImageKit Basic Auth only uses the `private_key` as username and ignores the password. * */ password?: string | null | undefined; @@ -183,7 +182,7 @@ export interface ClientOptions { * API Client for interfacing with the Image Kit API. */ export class ImageKit { - privateAPIKey: string; + privateKey: string; password: string | null; webhookSecret: string | null; @@ -202,7 +201,7 @@ export class ImageKit { /** * API Client for interfacing with the Image Kit API. * - * @param {string | undefined} [opts.privateAPIKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] + * @param {string | undefined} [opts.privateKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] * @param {string | null | undefined} [opts.password=process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] ?? do_not_set] * @param {string | null | undefined} [opts.webhookSecret=process.env['IMAGEKIT_WEBHOOK_SECRET'] ?? null] * @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API. @@ -215,19 +214,19 @@ export class ImageKit { */ constructor({ baseURL = readEnv('IMAGE_KIT_BASE_URL'), - privateAPIKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), + privateKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), password = readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS') ?? 'do_not_set', webhookSecret = readEnv('IMAGEKIT_WEBHOOK_SECRET') ?? null, ...opts }: ClientOptions = {}) { - if (privateAPIKey === undefined) { + if (privateKey === undefined) { throw new Errors.ImageKitError( - "The IMAGEKIT_PRIVATE_API_KEY environment variable is missing or empty; either provide it, or instantiate the ImageKit client with an privateAPIKey option, like new ImageKit({ privateAPIKey: 'My Private API Key' }).", + "The IMAGEKIT_PRIVATE_API_KEY environment variable is missing or empty; either provide it, or instantiate the ImageKit client with an privateKey option, like new ImageKit({ privateKey: 'My Private Key' }).", ); } const options: ClientOptions = { - privateAPIKey, + privateKey, password, webhookSecret, ...opts, @@ -251,7 +250,7 @@ export class ImageKit { this._options = options; - this.privateAPIKey = privateAPIKey; + this.privateKey = privateKey; this.password = password; this.webhookSecret = webhookSecret; } @@ -269,7 +268,7 @@ export class ImageKit { logLevel: this.logLevel, fetch: this.fetch, fetchOptions: this.fetchOptions, - privateAPIKey: this.privateAPIKey, + privateKey: this.privateKey, password: this.password, webhookSecret: this.webhookSecret, ...options, @@ -289,7 +288,7 @@ export class ImageKit { } protected validateHeaders({ values, nulls }: NullableHeaders) { - if (this.privateAPIKey && this.password && values.get('authorization')) { + if (this.privateKey && this.password && values.get('authorization')) { return; } if (nulls.has('authorization')) { @@ -297,12 +296,12 @@ export class ImageKit { } throw new Error( - 'Could not resolve authentication method. Expected the privateAPIKey or password to be set. Or for the "Authorization" headers to be explicitly omitted', + 'Could not resolve authentication method. Expected the privateKey or password to be set. Or for the "Authorization" headers to be explicitly omitted', ); } protected async authHeaders(opts: FinalRequestOptions): Promise { - if (!this.privateAPIKey) { + if (!this.privateKey) { return undefined; } @@ -310,7 +309,7 @@ export class ImageKit { return undefined; } - const credentials = `${this.privateAPIKey}:${this.password}`; + const credentials = `${this.privateKey}:${this.password}`; const Authorization = `Basic ${toBase64(credentials)}`; return buildHeaders([{ Authorization }]); } diff --git a/tests/api-resources/accounts/origins.test.ts b/tests/api-resources/accounts/origins.test.ts index 4aee2b5d..f912c7ea 100644 --- a/tests/api-resources/accounts/origins.test.ts +++ b/tests/api-resources/accounts/origins.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/accounts/url-endpoints.test.ts b/tests/api-resources/accounts/url-endpoints.test.ts index 4f09652b..eea18604 100644 --- a/tests/api-resources/accounts/url-endpoints.test.ts +++ b/tests/api-resources/accounts/url-endpoints.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/accounts/usage.test.ts b/tests/api-resources/accounts/usage.test.ts index 5e83c3c5..98f51e3f 100644 --- a/tests/api-resources/accounts/usage.test.ts +++ b/tests/api-resources/accounts/usage.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/assets.test.ts b/tests/api-resources/assets.test.ts index a67f9a34..4688d817 100644 --- a/tests/api-resources/assets.test.ts +++ b/tests/api-resources/assets.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/beta/v2/files.test.ts b/tests/api-resources/beta/v2/files.test.ts index 1012110e..0faf8d31 100644 --- a/tests/api-resources/beta/v2/files.test.ts +++ b/tests/api-resources/beta/v2/files.test.ts @@ -3,7 +3,7 @@ import ImageKit, { toFile } from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/cache/invalidation.test.ts b/tests/api-resources/cache/invalidation.test.ts index 0354e08e..02f3d4cc 100644 --- a/tests/api-resources/cache/invalidation.test.ts +++ b/tests/api-resources/cache/invalidation.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/custom-metadata-fields.test.ts b/tests/api-resources/custom-metadata-fields.test.ts index 9ebf4d3a..cbc88761 100644 --- a/tests/api-resources/custom-metadata-fields.test.ts +++ b/tests/api-resources/custom-metadata-fields.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/files/bulk.test.ts b/tests/api-resources/files/bulk.test.ts index 823b35b6..c5c290a7 100644 --- a/tests/api-resources/files/bulk.test.ts +++ b/tests/api-resources/files/bulk.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/files/files.test.ts b/tests/api-resources/files/files.test.ts index 709c6214..3187856d 100644 --- a/tests/api-resources/files/files.test.ts +++ b/tests/api-resources/files/files.test.ts @@ -3,7 +3,7 @@ import ImageKit, { toFile } from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/files/metadata.test.ts b/tests/api-resources/files/metadata.test.ts index 8b2c74cc..b7da8ebf 100644 --- a/tests/api-resources/files/metadata.test.ts +++ b/tests/api-resources/files/metadata.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/files/versions.test.ts b/tests/api-resources/files/versions.test.ts index 24800f8c..c1bb6d4c 100644 --- a/tests/api-resources/files/versions.test.ts +++ b/tests/api-resources/files/versions.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/folders/folders.test.ts b/tests/api-resources/folders/folders.test.ts index e42b6414..a983ec4a 100644 --- a/tests/api-resources/folders/folders.test.ts +++ b/tests/api-resources/folders/folders.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/folders/job.test.ts b/tests/api-resources/folders/job.test.ts index 3e698206..e15071f7 100644 --- a/tests/api-resources/folders/job.test.ts +++ b/tests/api-resources/folders/job.test.ts @@ -3,7 +3,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/webhooks.test.ts b/tests/api-resources/webhooks.test.ts index 0d223f49..8fd83de9 100644 --- a/tests/api-resources/webhooks.test.ts +++ b/tests/api-resources/webhooks.test.ts @@ -5,7 +5,7 @@ import { Webhook } from 'standardwebhooks'; import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/index.test.ts b/tests/index.test.ts index d3799f72..f3f1a3de 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -23,7 +23,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-My-Default-Header': '2' }, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -91,7 +91,7 @@ describe('instantiate client', () => { const client = new ImageKit({ logger: logger, logLevel: 'debug', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -100,7 +100,7 @@ describe('instantiate client', () => { }); test('default logLevel is warn', async () => { - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.logLevel).toBe('warn'); }); @@ -116,7 +116,7 @@ describe('instantiate client', () => { const client = new ImageKit({ logger: logger, logLevel: 'info', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -134,11 +134,7 @@ describe('instantiate client', () => { }; process.env['IMAGE_KIT_LOG'] = 'debug'; - const client = new ImageKit({ - logger: logger, - privateAPIKey: 'My Private API Key', - password: 'My Password', - }); + const client = new ImageKit({ logger: logger, privateKey: 'My Private Key', password: 'My Password' }); expect(client.logLevel).toBe('debug'); await forceAPIResponseForClient(client); @@ -155,11 +151,7 @@ describe('instantiate client', () => { }; process.env['IMAGE_KIT_LOG'] = 'not a log level'; - const client = new ImageKit({ - logger: logger, - privateAPIKey: 'My Private API Key', - password: 'My Password', - }); + const client = new ImageKit({ logger: logger, privateKey: 'My Private Key', password: 'My Password' }); expect(client.logLevel).toBe('warn'); expect(warnMock).toHaveBeenCalledWith( 'process.env[\'IMAGE_KIT_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', @@ -179,7 +171,7 @@ describe('instantiate client', () => { const client = new ImageKit({ logger: logger, logLevel: 'off', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -200,7 +192,7 @@ describe('instantiate client', () => { const client = new ImageKit({ logger: logger, logLevel: 'debug', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.logLevel).toBe('debug'); @@ -213,7 +205,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo' }, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); @@ -223,7 +215,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo', hello: 'world' }, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); @@ -233,7 +225,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', defaultQuery: { hello: 'world' }, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); @@ -243,7 +235,7 @@ describe('instantiate client', () => { test('custom fetch', async () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: (url) => { return Promise.resolve( @@ -262,7 +254,7 @@ describe('instantiate client', () => { // make sure the global fetch type is assignable to our Fetch type const client = new ImageKit({ baseURL: 'http://localhost:5000/', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: defaultFetch, }); @@ -271,7 +263,7 @@ describe('instantiate client', () => { test('custom signal', async () => { const client = new ImageKit({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: (...args) => { return new Promise((resolve, reject) => @@ -304,7 +296,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: testFetch, }); @@ -317,7 +309,7 @@ describe('instantiate client', () => { test('trailing slash', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/custom/path/', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); @@ -326,7 +318,7 @@ describe('instantiate client', () => { test('no trailing slash', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/custom/path', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); @@ -339,7 +331,7 @@ describe('instantiate client', () => { test('explicit option', () => { const client = new ImageKit({ baseURL: 'https://example.com', - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); expect(client.baseURL).toEqual('https://example.com'); @@ -347,24 +339,24 @@ describe('instantiate client', () => { test('env variable', () => { process.env['IMAGE_KIT_BASE_URL'] = 'https://example.com/from_env'; - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.baseURL).toEqual('https://example.com/from_env'); }); test('empty env variable', () => { process.env['IMAGE_KIT_BASE_URL'] = ''; // empty - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.baseURL).toEqual('https://api.imagekit.io'); }); test('blank env variable', () => { process.env['IMAGE_KIT_BASE_URL'] = ' '; // blank - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.baseURL).toEqual('https://api.imagekit.io'); }); test('in request options', () => { - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 'http://localhost:5000/option/foo', ); @@ -372,7 +364,7 @@ describe('instantiate client', () => { test('in request options overridden by client options', () => { const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', baseURL: 'http://localhost:5000/client', }); @@ -383,7 +375,7 @@ describe('instantiate client', () => { test('in request options overridden by env variable', () => { process.env['IMAGE_KIT_BASE_URL'] = 'http://localhost:5000/env'; - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 'http://localhost:5000/env/foo', ); @@ -391,15 +383,11 @@ describe('instantiate client', () => { }); test('maxRetries option is correctly set', () => { - const client = new ImageKit({ - maxRetries: 4, - privateAPIKey: 'My Private API Key', - password: 'My Password', - }); + const client = new ImageKit({ maxRetries: 4, privateKey: 'My Private Key', password: 'My Password' }); expect(client.maxRetries).toEqual(4); // default - const client2 = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client2 = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client2.maxRetries).toEqual(2); }); @@ -408,7 +396,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', maxRetries: 3, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -435,7 +423,7 @@ describe('instantiate client', () => { baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-Test-Header': 'test-value' }, defaultQuery: { 'test-param': 'test-value' }, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -454,7 +442,7 @@ describe('instantiate client', () => { const client = new ImageKit({ baseURL: 'http://localhost:5000/', timeout: 1000, - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', }); @@ -484,25 +472,25 @@ describe('instantiate client', () => { test('with environment variable arguments', () => { // set options via env var - process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'My Private API Key'; + process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'My Private Key'; process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'My Password'; const client = new ImageKit(); - expect(client.privateAPIKey).toBe('My Private API Key'); + expect(client.privateKey).toBe('My Private Key'); expect(client.password).toBe('My Password'); }); test('with overridden environment variable arguments', () => { // set options via env var - process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'another My Private API Key'; + process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'another My Private Key'; process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'another My Password'; - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); - expect(client.privateAPIKey).toBe('My Private API Key'); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); + expect(client.privateKey).toBe('My Private Key'); expect(client.password).toBe('My Password'); }); }); describe('request building', () => { - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); describe('custom headers', () => { test('handles undefined', async () => { @@ -521,7 +509,7 @@ describe('request building', () => { }); describe('default encoder', () => { - const client = new ImageKit({ privateAPIKey: 'My Private API Key', password: 'My Password' }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); class Serializable { toJSON() { @@ -607,7 +595,7 @@ describe('retries', () => { }; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', timeout: 10, fetch: testFetch, @@ -642,7 +630,7 @@ describe('retries', () => { }; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: testFetch, maxRetries: 4, @@ -671,7 +659,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: testFetch, maxRetries: 4, @@ -705,7 +693,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: testFetch, maxRetries: 4, @@ -739,7 +727,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private Key', password: 'My Password', fetch: testFetch, maxRetries: 4, @@ -773,11 +761,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new ImageKit({ - privateAPIKey: 'My Private API Key', - password: 'My Password', - fetch: testFetch, - }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password', fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); @@ -807,11 +791,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new ImageKit({ - privateAPIKey: 'My Private API Key', - password: 'My Password', - fetch: testFetch, - }); + const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password', fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); From d0d45ee5351438651649153cb70ed8d9809078a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:45:39 +0000 Subject: [PATCH 58/73] feat(api): manual updates --- .github/workflows/ci.yml | 18 +- .gitignore | 3 +- .prettierignore | 2 +- .stats.yml | 2 +- eslint.config.mjs | 2 +- packages/mcp-server/README.md | 365 ++ packages/mcp-server/build | 56 + .../mcp-server/cloudflare-worker/.gitignore | 178 + .../mcp-server/cloudflare-worker/README.md | 100 + .../mcp-server/cloudflare-worker/biome.json | 34 + .../mcp-server/cloudflare-worker/package.json | 27 + .../mcp-server/cloudflare-worker/src/app.ts | 106 + .../mcp-server/cloudflare-worker/src/index.ts | 109 + .../mcp-server/cloudflare-worker/src/utils.ts | 480 ++ .../cloudflare-worker/static/home.md | 86 + .../cloudflare-worker/tsconfig.json | 19 + .../worker-configuration.d.ts | 5707 +++++++++++++++++ .../cloudflare-worker/wrangler.jsonc | 35 + packages/mcp-server/jest.config.ts | 17 + packages/mcp-server/manifest.json | 57 + packages/mcp-server/package.json | 86 + .../mcp-server/scripts/copy-bundle-files.cjs | 36 + .../scripts/postprocess-dist-package-json.cjs | 12 + packages/mcp-server/src/code-tool-paths.cts | 3 + packages/mcp-server/src/code-tool-types.ts | 14 + packages/mcp-server/src/code-tool-worker.ts | 46 + packages/mcp-server/src/code-tool.ts | 148 + packages/mcp-server/src/compat.ts | 483 ++ packages/mcp-server/src/dynamic-tools.ts | 153 + packages/mcp-server/src/filtering.ts | 14 + packages/mcp-server/src/headers.ts | 31 + packages/mcp-server/src/http.ts | 127 + packages/mcp-server/src/index.ts | 108 + packages/mcp-server/src/options.ts | 456 ++ packages/mcp-server/src/server.ts | 204 + packages/mcp-server/src/stdio.ts | 13 + packages/mcp-server/src/tools.ts | 1 + .../origins/create-accounts-origins.ts | 318 + .../origins/delete-accounts-origins.ts | 43 + .../accounts/origins/get-accounts-origins.ts | 41 + .../accounts/origins/list-accounts-origins.ts | 35 + .../origins/update-accounts-origins.ts | 360 ++ .../create-accounts-url-endpoints.ts | 103 + .../delete-accounts-url-endpoints.ts | 43 + .../get-accounts-url-endpoints.ts | 49 + .../list-accounts-url-endpoints.ts | 44 + .../update-accounts-url-endpoints.ts | 112 + .../accounts/usage/get-accounts-usage.ts | 56 + .../src/tools/assets/list-assets.ts | 94 + .../beta/v2/files/upload-v2-beta-files.ts | 315 + .../invalidation/create-cache-invalidation.ts | 46 + .../invalidation/get-cache-invalidation.ts | 47 + .../create-custom-metadata-fields.ts | 154 + .../delete-custom-metadata-fields.ts | 47 + .../list-custom-metadata-fields.ts | 48 + .../update-custom-metadata-fields.ts | 150 + .../tools/files/bulk/add-tags-files-bulk.ts | 56 + .../src/tools/files/bulk/delete-files-bulk.ts | 49 + .../files/bulk/remove-ai-tags-files-bulk.ts | 56 + .../files/bulk/remove-tags-files-bulk.ts | 56 + .../mcp-server/src/tools/files/copy-files.ts | 55 + .../src/tools/files/delete-files.ts | 41 + .../mcp-server/src/tools/files/get-files.ts | 47 + .../files/metadata/get-files-metadata.ts | 47 + .../metadata/get-from-url-files-metadata.ts | 48 + .../mcp-server/src/tools/files/move-files.ts | 50 + .../src/tools/files/rename-files.ts | 58 + .../src/tools/files/update-files.ts | 196 + .../src/tools/files/upload-files.ts | 331 + .../files/versions/delete-files-versions.ts | 52 + .../files/versions/get-files-versions.ts | 50 + .../files/versions/list-files-versions.ts | 47 + .../files/versions/restore-files-versions.ts | 52 + .../src/tools/folders/copy-folders.ts | 55 + .../src/tools/folders/create-folders.ts | 52 + .../src/tools/folders/delete-folders.ts | 48 + .../src/tools/folders/job/get-folders-job.ts | 47 + .../src/tools/folders/move-folders.ts | 50 + .../src/tools/folders/rename-folders.ts | 56 + packages/mcp-server/src/tools/index.ts | 153 + packages/mcp-server/src/tools/types.ts | 103 + packages/mcp-server/tests/compat.test.ts | 1166 ++++ .../mcp-server/tests/dynamic-tools.test.ts | 185 + packages/mcp-server/tests/options.test.ts | 518 ++ packages/mcp-server/tests/tools.test.ts | 225 + packages/mcp-server/tsc-multi.json | 7 + packages/mcp-server/tsconfig.build.json | 18 + packages/mcp-server/tsconfig.dist-src.json | 11 + packages/mcp-server/tsconfig.json | 37 + packages/mcp-server/yarn.lock | 3916 +++++++++++ release-please-config.json | 11 +- scripts/build | 6 + scripts/build-all | 5 + scripts/publish-packages.ts | 102 + scripts/utils/make-dist-package-json.cjs | 8 + src/client.ts | 16 +- 96 files changed, 19515 insertions(+), 14 deletions(-) create mode 100644 packages/mcp-server/README.md create mode 100644 packages/mcp-server/build create mode 100644 packages/mcp-server/cloudflare-worker/.gitignore create mode 100644 packages/mcp-server/cloudflare-worker/README.md create mode 100644 packages/mcp-server/cloudflare-worker/biome.json create mode 100644 packages/mcp-server/cloudflare-worker/package.json create mode 100644 packages/mcp-server/cloudflare-worker/src/app.ts create mode 100644 packages/mcp-server/cloudflare-worker/src/index.ts create mode 100644 packages/mcp-server/cloudflare-worker/src/utils.ts create mode 100644 packages/mcp-server/cloudflare-worker/static/home.md create mode 100644 packages/mcp-server/cloudflare-worker/tsconfig.json create mode 100644 packages/mcp-server/cloudflare-worker/worker-configuration.d.ts create mode 100644 packages/mcp-server/cloudflare-worker/wrangler.jsonc create mode 100644 packages/mcp-server/jest.config.ts create mode 100644 packages/mcp-server/manifest.json create mode 100644 packages/mcp-server/package.json create mode 100644 packages/mcp-server/scripts/copy-bundle-files.cjs create mode 100644 packages/mcp-server/scripts/postprocess-dist-package-json.cjs create mode 100644 packages/mcp-server/src/code-tool-paths.cts create mode 100644 packages/mcp-server/src/code-tool-types.ts create mode 100644 packages/mcp-server/src/code-tool-worker.ts create mode 100644 packages/mcp-server/src/code-tool.ts create mode 100644 packages/mcp-server/src/compat.ts create mode 100644 packages/mcp-server/src/dynamic-tools.ts create mode 100644 packages/mcp-server/src/filtering.ts create mode 100644 packages/mcp-server/src/headers.ts create mode 100644 packages/mcp-server/src/http.ts create mode 100644 packages/mcp-server/src/index.ts create mode 100644 packages/mcp-server/src/options.ts create mode 100644 packages/mcp-server/src/server.ts create mode 100644 packages/mcp-server/src/stdio.ts create mode 100644 packages/mcp-server/src/tools.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts create mode 100644 packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts create mode 100644 packages/mcp-server/src/tools/assets/list-assets.ts create mode 100644 packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts create mode 100644 packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts create mode 100644 packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts create mode 100644 packages/mcp-server/src/tools/files/copy-files.ts create mode 100644 packages/mcp-server/src/tools/files/delete-files.ts create mode 100644 packages/mcp-server/src/tools/files/get-files.ts create mode 100644 packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts create mode 100644 packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts create mode 100644 packages/mcp-server/src/tools/files/move-files.ts create mode 100644 packages/mcp-server/src/tools/files/rename-files.ts create mode 100644 packages/mcp-server/src/tools/files/update-files.ts create mode 100644 packages/mcp-server/src/tools/files/upload-files.ts create mode 100644 packages/mcp-server/src/tools/files/versions/delete-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/get-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/list-files-versions.ts create mode 100644 packages/mcp-server/src/tools/files/versions/restore-files-versions.ts create mode 100644 packages/mcp-server/src/tools/folders/copy-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/create-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/delete-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/job/get-folders-job.ts create mode 100644 packages/mcp-server/src/tools/folders/move-folders.ts create mode 100644 packages/mcp-server/src/tools/folders/rename-folders.ts create mode 100644 packages/mcp-server/src/tools/index.ts create mode 100644 packages/mcp-server/src/tools/types.ts create mode 100644 packages/mcp-server/tests/compat.test.ts create mode 100644 packages/mcp-server/tests/dynamic-tools.test.ts create mode 100644 packages/mcp-server/tests/options.test.ts create mode 100644 packages/mcp-server/tests/tools.test.ts create mode 100644 packages/mcp-server/tsc-multi.json create mode 100644 packages/mcp-server/tsconfig.build.json create mode 100644 packages/mcp-server/tsconfig.dist-src.json create mode 100644 packages/mcp-server/tsconfig.json create mode 100644 packages/mcp-server/yarn.lock create mode 100755 scripts/build-all create mode 100644 scripts/publish-packages.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f5e57c2..e34f886d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' - name: Bootstrap run: ./scripts/bootstrap @@ -46,7 +46,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' - name: Bootstrap run: ./scripts/bootstrap @@ -68,6 +68,15 @@ jobs: AUTH: ${{ steps.github-oidc.outputs.github_token }} SHA: ${{ github.sha }} run: ./scripts/utils/upload-artifact.sh + + - name: Upload MCP Server tarball + if: github.repository == 'stainless-sdks/imagekit-typescript' + env: + URL: https://pkg.stainless.com/s?subpackage=mcp-server + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + BASE_PATH: packages/mcp-server + run: ./scripts/utils/upload-artifact.sh test: timeout-minutes: 10 name: test @@ -79,10 +88,13 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' - name: Bootstrap run: ./scripts/bootstrap + - name: Build + run: ./scripts/build + - name: Run tests run: ./scripts/test diff --git a/.gitignore b/.gitignore index d98d51a8..74cba895 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist dist-deno /*.tgz .idea/ - +dist-bundle +*.mcpb diff --git a/.prettierignore b/.prettierignore index 3548c5af..7cc13dd1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,4 @@ CHANGELOG.md /deno # don't format tsc output, will break source maps -/dist +dist diff --git a/.stats.yml b/.stats.yml index 00fd95da..d3fcabb4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: 29a2351fe2be89392b15719be8bc964f +config_hash: c9c7bed2a4341f915a2dc85958ce7f0e diff --git a/eslint.config.mjs b/eslint.config.mjs index 68d1b0b9..c1a01a62 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -34,7 +34,7 @@ export default tseslint.config( }, }, { - files: ['tests/**', 'examples/**'], + files: ['tests/**', 'examples/**', 'packages/**'], rules: { 'no-restricted-imports': 'off', }, diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md new file mode 100644 index 00000000..1ccd5dbd --- /dev/null +++ b/packages/mcp-server/README.md @@ -0,0 +1,365 @@ +# Image Kit TypeScript MCP Server + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + +### Building + +Because it's not published yet, clone the repo and build it: + +```sh +git clone git@github.com:imagekit-developer/imagekit-nodejs.git +cd imagekit-nodejs +./scripts/bootstrap +./scripts/build +``` + +### Running + +```sh +# set env vars as needed +export IMAGEKIT_PRIVATE_API_KEY="My Private Key" +export OPTIONAL_IMAGEKIT_IGNORES_THIS="My Password" +export IMAGEKIT_WEBHOOK_SECRET="My Webhook Secret" +node ./packages/mcp-server/dist/index.js +``` + +> [!NOTE] +> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npx -y imagekit-api-mcp` + +### Via MCP Client + +[Build the project](#building) as mentioned above. + +There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already +have a client, consult their documentation to install the MCP server. + +For clients with a configuration JSON, it might look something like this: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "node", + "args": ["/path/to/local/imagekit-nodejs/packages/mcp-server", "--client=claude", "--tools=dynamic"], + "env": { + "IMAGEKIT_PRIVATE_API_KEY": "My Private Key", + "OPTIONAL_IMAGEKIT_IGNORES_THIS": "My Password", + "IMAGEKIT_WEBHOOK_SECRET": "My Webhook Secret" + } + } + } +} +``` + +## Exposing endpoints to your MCP Client + +There are two ways to expose endpoints as tools in the MCP server: + +1. Exposing one tool per endpoint, and filtering as necessary +2. Exposing a set of tools to dynamically discover and invoke endpoints from the API + +### Filtering endpoints and tools + +You can run the package on the command line to discover and filter the set of tools that are exposed by the +MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's +context window. + +You can filter by multiple aspects: + +- `--tool` includes a specific tool by name +- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` +- `--operation` includes just read (get/list) or just write operations + +### Dynamic tools + +If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will +expose the following tools: + +1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query +2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint +3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters + +This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all +of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to +search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it +can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, +you can opt-in to explicit tools, the dynamic tools, or both. + +See more information with `--help`. + +All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). + +Use `--list` to see the list of available tools, or see below. + +### Specifying the MCP Client + +Different clients have varying abilities to handle arbitrary tools and schemas. + +You can specify the client you are using with the `--client` argument, and the MCP server will automatically +serve tools and schemas that are more compatible with that client. + +- `--client=`: Set all capabilities based on a known MCP client + + - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` + - Example: `--client=cursor` + +Additionally, if you have a client not on the above list, or the client has gotten better +over time, you can manually enable or disable certain capabilities: + +- `--capability=`: Specify individual client capabilities + - Available capabilities: + - `top-level-unions`: Enable support for top-level unions in tool schemas + - `valid-json`: Enable JSON string parsing for arguments + - `refs`: Enable support for $ref pointers in schemas + - `unions`: Enable support for union types (anyOf) in schemas + - `formats`: Enable support for format validations in schemas (e.g. date-time, email) + - `tool-name-length=N`: Set maximum tool name length to N characters + - Example: `--capability=top-level-unions --capability=tool-name-length=40` + - Example: `--capability=top-level-unions,tool-name-length=40` + +### Examples + +1. Filter for read operations on cards: + +```bash +--resource=cards --operation=read +``` + +2. Exclude specific tools while including others: + +```bash +--resource=cards --no-tool=create_cards +``` + +3. Configure for Cursor client with custom max tool name length: + +```bash +--client=cursor --capability=tool-name-length=40 +``` + +4. Complex filtering with multiple criteria: + +```bash +--resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards +``` + +## Running remotely + +Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket. + +Authorization can be provided via the `Authorization` header using the Basic scheme. + +Additionally, authorization can be provided via the following headers: +| Header | Equivalent client option | Security scheme | +| ---------------------------------- | ------------------------ | --------------- | +| `x-imagekit-private-api-key` | `privateKey` | basicAuth | +| `x-optional-imagekit-ignores-this` | `password` | basicAuth | + +A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "url": "http://localhost:3000", + "headers": { + "Authorization": "Basic " + } + } + } +} +``` + +The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. +For example, to exclude specific tools while including others, use the URL: + +``` +http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards +``` + +Or, to configure for the Cursor client, with a custom max tool name length, use the URL: + +``` +http://localhost:3000?client=cursor&capability=tool-name-length%3D40 +``` + +## Importing the tools and server individually + +```js +// Import the server, generated endpoints, or the init function +import { server, endpoints, init } from "imagekit-api-mcp/server"; + +// import a specific tool +import createCustomMetadataFields from "imagekit-api-mcp/tools/custom-metadata-fields/create-custom-metadata-fields"; + +// initialize the server and all endpoints +init({ server, endpoints }); + +// manually start server +const transport = new StdioServerTransport(); +await server.connect(transport); + +// or initialize your own server with specific tools +const myServer = new McpServer(...); + +// define your own endpoint +const myCustomEndpoint = { + tool: { + name: 'my_custom_tool', + description: 'My custom tool', + inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), + }, + handler: async (client: client, args: any) => { + return { myResponse: 'Hello world!' }; + }) +}; + +// initialize the server with your custom endpoints +init({ server: myServer, endpoints: [createCustomMetadataFields, myCustomEndpoint] }); +``` + +## Available Tools + +The following tools are available in this MCP server. + +### Resource `customMetadataFields`: + +- `create_custom_metadata_fields` (`write`): This API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API. +- `update_custom_metadata_fields` (`write`): This API updates the label or schema of an existing custom metadata field. +- `list_custom_metadata_fields` (`read`): This API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response. +- `delete_custom_metadata_fields` (`write`): This API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name. + +### Resource `files`: + +- `update_files` (`write`): This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API. +- `delete_files` (`write`): This API deletes the file and all its file versions permanently. + + Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. + +- `copy_files` (`write`): This will copy a file from one folder to another. + + Note: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history. + +- `get_files` (`read`): This API returns an object with details or attributes about the current version of the file. +- `move_files` (`write`): This will move a file and all its versions from one folder to another. + + Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file. + +- `rename_files` (`write`): You can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. + + Note: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested. + +- `upload_files` (`write`): ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload. + + The [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT. + + **File size limit** \ + On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans. + + **Version limit** \ + A file can have a maximum of 100 versions. + + **Demo applications** + + - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. + - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. + +### Resource `files.bulk`: + +- `delete_files_bulk` (`write`): This API deletes multiple files and all their file versions permanently. + + Note: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API. + + A maximum of 100 files can be deleted at a time. + +- `add_tags_files_bulk` (`write`): This API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time. +- `remove_ai_tags_files_bulk` (`write`): This API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time. +- `remove_tags_files_bulk` (`write`): This API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time. + +### Resource `files.versions`: + +- `list_files_versions` (`read`): This API returns details of all versions of a file. +- `delete_files_versions` (`write`): This API deletes a non-current file version permanently. The API returns an empty response. + + Note: If you want to delete all versions of a file, use the delete file API. + +- `get_files_versions` (`read`): This API returns an object with details or attributes of a file version. +- `restore_files_versions` (`write`): This API restores a file version as the current file version. + +### Resource `files.metadata`: + +- `get_files_metadata` (`read`): You can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API. + + You can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter. + +- `get_from_url_files_metadata` (`read`): Get image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API. + +### Resource `assets`: + +- `list_assets` (`read`): This API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`. + +### Resource `cache.invalidation`: + +- `create_cache_invalidation` (`write`): This API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes. +- `get_cache_invalidation` (`read`): This API returns the status of a purge cache request. + +### Resource `folders`: + +- `create_folders` (`write`): This will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created. +- `delete_folders` (`write`): This will delete a folder and all its contents permanently. The API returns an empty response. +- `copy_folders` (`write`): This will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. +- `move_folders` (`write`): This will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history. +- `rename_folders` (`write`): This API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name. + +### Resource `folders.job`: + +- `get_folders_job` (`read`): This API returns the status of a bulk job like copy and move folder operations. + +### Resource `accounts.usage`: + +- `get_accounts_usage` (`read`): Get the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date. + +### Resource `accounts.origins`: + +- `create_accounts_origins` (`write`): **Note:** This API is currently in beta. + Creates a new origin and returns the origin object. +- `update_accounts_origins` (`write`): **Note:** This API is currently in beta. + Updates the origin identified by `id` and returns the updated origin object. +- `list_accounts_origins` (`read`): **Note:** This API is currently in beta. + Returns an array of all configured origins for the current account. +- `delete_accounts_origins` (`write`): **Note:** This API is currently in beta. + Permanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error. +- `get_accounts_origins` (`read`): **Note:** This API is currently in beta. + Retrieves the origin identified by `id`. + +### Resource `accounts.urlEndpoints`: + +- `create_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Creates a new URL‑endpoint and returns the resulting object. +- `update_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Updates the URL‑endpoint identified by `id` and returns the updated object. +- `list_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. + Returns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation. +- `delete_accounts_url_endpoints` (`write`): **Note:** This API is currently in beta. + Deletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation. +- `get_accounts_url_endpoints` (`read`): **Note:** This API is currently in beta. + Retrieves the URL‑endpoint identified by `id`. + +### Resource `beta.v2.files`: + +- `upload_v2_beta_files` (`write`): The V2 API enhances security by verifying the entire payload using JWT. This API is in beta. + + ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload. + + **File size limit** \ + On the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans. + + **Version limit** \ + A file can have a maximum of 100 versions. + + **Demo applications** + + - A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more. + - [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies. diff --git a/packages/mcp-server/build b/packages/mcp-server/build new file mode 100644 index 00000000..4c6474cc --- /dev/null +++ b/packages/mcp-server/build @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -exuo pipefail + +rm -rf dist; mkdir dist + +# Copy src to dist/src and build from dist/src into dist, so that +# the source map for index.js.map will refer to ./src/index.ts etc +cp -rp src README.md dist + +for file in LICENSE; do + if [ -e "../../${file}" ]; then cp "../../${file}" dist; fi +done + +for file in CHANGELOG.md; do + if [ -e "${file}" ]; then cp "${file}" dist; fi +done + +# this converts the export map paths for the dist directory +# and does a few other minor things +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist/package.json + +# updates the `@imagekit/nodejs` dependency to point to NPM +node scripts/postprocess-dist-package-json.cjs + +# build to .js/.mjs/.d.ts files +./node_modules/.bin/tsc-multi + +cp tsconfig.dist-src.json dist/src/tsconfig.json + +chmod +x dist/index.js + +DIST_PATH=./dist PKG_IMPORT_PATH=imagekit-api-mcp/ node ../../scripts/utils/postprocess-files.cjs + +# mcp bundle +rm -rf dist-bundle imagekit_nodejs_api.mcpb; mkdir dist-bundle + +# copy package.json +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist-bundle/package.json + +# copy files +node scripts/copy-bundle-files.cjs + +# install runtime deps +cd dist-bundle +npm install +cd .. + +# pack bundle +cp manifest.json dist-bundle + +npx mcpb pack dist-bundle imagekit_nodejs_api.mcpb + +npx mcpb sign imagekit_nodejs_api.mcpb --self-signed + +# clean up +rm -rf dist-bundle diff --git a/packages/mcp-server/cloudflare-worker/.gitignore b/packages/mcp-server/cloudflare-worker/.gitignore new file mode 100644 index 00000000..5addd9af --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/.gitignore @@ -0,0 +1,178 @@ +node_modules + +.nx +.idea +.vscode +.zed +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/packages/mcp-server/cloudflare-worker/README.md b/packages/mcp-server/cloudflare-worker/README.md new file mode 100644 index 00000000..fc89a91d --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/README.md @@ -0,0 +1,100 @@ +# Remote MCP Server on Cloudflare with Stainless + +Remote MCP servers require OAuth, so this flow implements a local version of the OAuth redirects, but instead accepts the +API token and any other client configuration options that you'd need to instantiate your TypeScript client. + +## Usage + +The recommended way to use this project is to use the below "deploy to cloudflare" button to use this repo as a template for generating a server. + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server/cloudflare-worker) + +## Develop locally + +```bash +# install dependencies +npm install + +# run locally +npm run dev +``` + +You should be able to open [`http://localhost:8787/`](http://localhost:8787/) in your browser + +## Connect the MCP inspector to your server + +To explore your new MCP api, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector). + +- Start it with `npx @modelcontextprotocol/inspector` +- [Within the inspector](http://localhost:5173), switch the Transport Type to `SSE` and enter `http://localhost:8787/sse` as the URL of the MCP server to connect to, and click "Connect" +- You will navigate to a (mock) user/password login screen. Input any email and pass to login. +- You should be redirected back to the MCP Inspector and you can now list and call any defined tools! + +## Connect Claude Desktop to your local MCP server + +The MCP inspector is great, but we really want to connect this to Claude! Follow [Anthropic's Quickstart](https://modelcontextprotocol.io/quickstart/user) and within Claude Desktop go to Settings > Developer > Edit Config to find your configuration file. + +Open the file in your text editor and replace it with this configuration: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "npx", + "args": ["mcp-remote", "http://localhost:8787/sse"] + } + } +} +``` + +This will run a local proxy and let Claude talk to your MCP server over HTTP + +When you open Claude a browser window should open and allow you to login. You should see the tools available in the bottom right. Given the right prompt Claude should ask to call the tool. + +## Deploy to Cloudflare + +If you want to manually deploy this server (e.g. without the "deploy to cloudflare" button) + +1. `npx wrangler@latest kv namespace create remote-mcp-server-oauth-kv` +2. Follow the guidance to add the kv namespace ID to `wrangler.jsonc` +3. `npm run deploy` + +## Call your newly deployed remote MCP server from a remote MCP client + +Just like you did above in "Develop locally", run the MCP inspector: + +`npx @modelcontextprotocol/inspector@latest` + +Then enter the `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) of your Worker in the inspector as the URL of the MCP server to connect to, and click "Connect". + +You've now connected to your MCP server from a remote MCP client. + +## Connect Claude Desktop to your remote MCP server + +Update the Claude configuration file to point to your `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) and restart Claude + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "npx", + "args": ["mcp-remote", "https://worker-name.account-name.workers.dev/sse"] + } + } +} +``` + +## Debugging + +Should anything go wrong it can be helpful to restart Claude, or to try connecting directly to your +MCP server on the command line with the following command. + +```bash +npx mcp-remote http://localhost:8787/sse +``` + +In some rare cases it may help to clear the files added to `~/.mcp-auth` + +```bash +rm -rf ~/.mcp-auth +``` diff --git a/packages/mcp-server/cloudflare-worker/biome.json b/packages/mcp-server/cloudflare-worker/biome.json new file mode 100644 index 00000000..13b23ad3 --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/biome.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.6.2/schema.json", + "organizeImports": { + "enabled": true + }, + "files": { + "ignore": ["worker-configuration.d.ts"] + }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off", + "noDebugger": "off", + "noConsoleLog": "off", + "noConfusingVoidType": "off" + }, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentWidth": 4, + "lineWidth": 100 + } +} diff --git a/packages/mcp-server/cloudflare-worker/package.json b/packages/mcp-server/cloudflare-worker/package.json new file mode 100644 index 00000000..ee97af9e --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/package.json @@ -0,0 +1,27 @@ +{ + "name": "remote-mcp-server-with-stainless", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "format": "biome format --write", + "lint:fix": "biome lint --fix", + "start": "wrangler dev", + "cf-typegen": "wrangler types" + }, + "devDependencies": { + "marked": "^15.0.11", + "typescript": "^5.8.3", + "workers-mcp": "0.1.0-3", + "wrangler": "^4.15.2" + }, + "dependencies": { + "@cloudflare/workers-oauth-provider": "^0.0.5", + "@modelcontextprotocol/sdk": "^1.11.4", + "agents": "^0.0.88", + "hono": "^4.7.9", + "imagekit-api-mcp": "latest", + "zod": "^3.24.4" + } +} diff --git a/packages/mcp-server/cloudflare-worker/src/app.ts b/packages/mcp-server/cloudflare-worker/src/app.ts new file mode 100644 index 00000000..227b4523 --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/src/app.ts @@ -0,0 +1,106 @@ +import { Hono } from 'hono'; +import { + layout, + homeContent, + parseApproveFormBody, + renderAuthorizationApprovedContent, + renderLoggedOutAuthorizeScreen, + renderAuthorizationRejectedContent, +} from './utils'; +import type { OAuthHelpers } from '@cloudflare/workers-oauth-provider'; +import { McpOptions } from 'imagekit-api-mcp/server'; +import { ServerConfig } from '.'; + +export type Bindings = Env & { + OAUTH_PROVIDER: OAuthHelpers; +}; + +export function makeOAuthConsent(config: ServerConfig, defaultOptions?: Partial) { + const app = new Hono<{ + Bindings: Bindings; + }>(); + + // Render a reasonable home page just to show the app is up + app.get('/', async (c) => { + const content = await homeContent(c.req.raw); + return c.html(layout(content, 'Home', config)); + }); + + // The /authorize page has a form that will POST to /approve + app.get('/authorize', async (c) => { + const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw); + + const content = await renderLoggedOutAuthorizeScreen(config, oauthReqInfo, defaultOptions); + return c.html(layout(content, 'Authorization', config)); + }); + + // This endpoint is responsible for validating any login information and + // then completing the authorization request with the OAUTH_PROVIDER + app.post('/approve', async (c) => { + const { action, oauthReqInfo, clientProps, clientConfig } = await parseApproveFormBody( + await c.req.parseBody(), + config, + ); + + if (action !== 'login_approve') { + return c.html( + layout( + await renderAuthorizationRejectedContent(oauthReqInfo?.redirectUri || ''), + 'Authorization Status', + config, + ), + ); + } + + if (!oauthReqInfo || !clientProps || !clientConfig) { + return c.html('INVALID LOGIN', 401); + } + + // We don't have a real user ID, just tokens, so we generate a random one + // Make this some stable ID if you want to look up the user's grants later. + const generatedUserId = crypto.randomUUID(); + + const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ + request: oauthReqInfo, + userId: generatedUserId, + metadata: {}, + scope: oauthReqInfo.scope, + props: { + clientProps, + clientConfig, + }, + }); + + return c.html( + layout(await renderAuthorizationApprovedContent(redirectTo), 'Authorization Status', config), + ); + }); + + // Render the authorize screen for demoing the OAuth flow (it won't actually log in) + app.get('/demo', async (c) => { + const content = await renderLoggedOutAuthorizeScreen(config, {} as any, defaultOptions); + return c.html(layout(content, 'Authorization', config)); + }); + + // Add a resource server .well-known to point clients to the correct auth server + const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Max-Age': '86400', + }; + app.options('/.well-known/oauth-protected-resource', async (c) => { + Object.entries(corsHeaders).forEach(([key, value]) => c.header(key, value)); + return c.body(null, 204); + }); + app.get('/.well-known/oauth-protected-resource', async (c) => { + Object.entries(corsHeaders).forEach(([key, value]) => c.header(key, value)); + const baseURL = new URL('/', c.req.url).toString(); + return c.json({ + resource: baseURL, + authorization_servers: [baseURL], + }); + }); + + return app; +} diff --git a/packages/mcp-server/cloudflare-worker/src/index.ts b/packages/mcp-server/cloudflare-worker/src/index.ts new file mode 100644 index 00000000..7f7a1de9 --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/src/index.ts @@ -0,0 +1,109 @@ +import { makeOAuthConsent } from './app'; +import { McpAgent } from 'agents/mcp'; +import OAuthProvider from '@cloudflare/workers-oauth-provider'; +import { McpOptions, initMcpServer, server, ClientOptions } from 'imagekit-api-mcp/server'; + +type MCPProps = { + clientProps: ClientOptions; + clientConfig: McpOptions; +}; + +/** + * The information displayed on the OAuth consent screen + */ +const serverConfig: ServerConfig = { + orgName: 'ImageKit', + instructionsUrl: undefined, // Set a url for where you show users how to get an API key + logoUrl: undefined, // Set a custom logo url to appear during the OAuth flow + clientProperties: [ + { + key: 'privateKey', + label: 'Private Key', + description: + 'Your ImageKit private API key (starts with `private_`).\nYou can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).\n', + required: true, + default: undefined, + placeholder: 'My Private Key', + type: 'password', + }, + { + key: 'password', + label: 'Password', + description: + 'Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password.\nThe password field is automatically managed by the SDK and should not be set.\n', + required: false, + default: 'do_not_set', + placeholder: 'My Password', + type: 'password', + }, + { + key: 'webhookSecret', + label: 'Webhook Secret', + description: + 'Your ImageKit webhook secret used by the SDK to verify webhook signatures for security.\nThis secret starts with a `whsec_` prefix and is essential for webhook verification.\nYou can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\n\n**Security Note**: Treat this secret like a password - keep it private and never expose it publicly.\nThis field is optional and only required if you plan to use webhook signature verification.\nLearn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature).\n', + required: false, + default: null, + placeholder: 'My Webhook Secret', + type: 'string', + }, + ], +}; + +export class MyMCP extends McpAgent { + server = server; + + async init() { + initMcpServer({ + server: this.server, + clientOptions: this.props.clientProps, + mcpOptions: this.props.clientConfig, + }); + } +} + +export type ServerConfig = { + /** + * The name of the company/project + */ + orgName: string; + + /** + * An optional company logo image + */ + logoUrl?: string; + + /** + * An optional URL with instructions for users to get an API key + */ + instructionsUrl?: string; + + /** + * Properties collected to initialize the client + */ + clientProperties: ClientProperty[]; +}; + +export type ClientProperty = { + key: string; + label: string; + description?: string; + required: boolean; + default?: unknown; + placeholder?: string; + type: 'string' | 'number' | 'password' | 'select'; + options?: { label: string; value: string }[]; +}; + +// Export the OAuth handler as the default +export default new OAuthProvider({ + apiHandlers: { + // @ts-expect-error + '/sse': MyMCP.serveSSE('/sse'), // legacy SSE + // @ts-expect-error + '/mcp': MyMCP.serve('/mcp'), // Streaming HTTP + }, + defaultHandler: makeOAuthConsent(serverConfig), + authorizeEndpoint: '/authorize', + tokenEndpoint: '/token', + clientRegistrationEndpoint: '/register', +}); diff --git a/packages/mcp-server/cloudflare-worker/src/utils.ts b/packages/mcp-server/cloudflare-worker/src/utils.ts new file mode 100644 index 00000000..7076424d --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/src/utils.ts @@ -0,0 +1,480 @@ +// Helper to generate the layout +import { html, raw } from 'hono/html'; +import type { HtmlEscapedString } from 'hono/utils/html'; +import { marked } from 'marked'; +import type { AuthRequest } from '@cloudflare/workers-oauth-provider'; +import { env } from 'cloudflare:workers'; +import { ServerConfig, McpOptions, ClientType, Filter, ClientProperty } from 'imagekit-api-mcp/server'; + +export const layout = (content: HtmlEscapedString | string, title: string, config: ServerConfig) => html` + + + + + + ${title} - ${config.orgName} MCP server + + + + + +

${content}
+
+
+

© ${new Date().getFullYear()} ${config.orgName}. All rights reserved.

+
+
+ + +`; + +export const homeContent = async (req: Request): Promise => { + // We have the README symlinked into the static directory, so we can fetch it + // and render it into HTML + const origin = new URL(req.url).origin; + const res = await env.ASSETS.fetch(`${origin}/home.md`); + let markdown = await res.text(); + markdown = markdown.replaceAll('{{cloudflareWorkerUrl}}', origin + '/sse'); + const content = await marked(markdown); + return html`
${raw(content)}
`; +}; + +export const renderLoggedOutAuthorizeScreen = async ( + config: ServerConfig, + oauthReqInfo: AuthRequest, + defaultOptions?: Partial, +) => { + const checked = (condition: boolean) => (condition ? 'checked' : ''); + const selected = (condition: boolean) => (condition ? 'selected' : ''); + + // Helper to check if a capability is enabled by default + const hasCapability = (capability: string) => { + if (!defaultOptions?.capabilities) { + // Default capabilities when none specified + return ['refs', 'unions', 'formats'].includes(capability); + } + switch (capability) { + case 'top-level-unions': + return defaultOptions.capabilities.topLevelUnions || false; + case 'valid-json': + return defaultOptions.capabilities.validJson || false; + case 'refs': + return defaultOptions.capabilities.refs || false; + case 'unions': + return defaultOptions.capabilities.unions || false; + case 'formats': + return defaultOptions.capabilities.formats || false; + default: + return false; + } + }; + + // Helper to check if an operation is enabled by default + const hasOperation = (operation: string) => { + if (!defaultOptions?.filters) { + // Default operations when none specified + return ['read', 'write'].includes(operation); + } + return defaultOptions.filters.some( + (f) => f.type === 'operation' && f.op === 'include' && f.value === operation, + ); + }; + const renderField = (field: ClientProperty) => { + if (field.type === 'select' && field.options) { + return html` +
+ + +
+ `; + } + return html` +
+ + +
+ `; + }; + + return html` +
+ ${config.logoUrl ? html`` : ''} + +

+ Authorizing ${config.orgName} MCP server +

+ +
+

+ Enter your credentials to initialize the connection with your MCP client. +

+ If you're not sure how to configure your client, see the + ${config.instructionsUrl ? + html`instructions` + : 'instructions'} + to get started. +
+
+ +
${config.clientProperties.map(renderField)}
+ +
+
+ + Configuration Options + +
+
+ + +
+ +
+ + +
+ + ? + +
+ Have the LLM dynamically discover the endpoints, instead of directly exposing one tool per + endpoint. +
+
+
+ +
+
+ + +
+ + ? + +
+ Restrict the available tools to only be able to read data. +
+
+
+
+
+
+
+ + + +
+
+ `; +}; + +export const renderApproveContent = async (message: string, status: string, redirectUrl: string) => { + return html` +
+
+ + ${status === 'success' ? '✓' : '✗'} + +
+

${message}

+

You will be redirected back to the application shortly.

+ + Return to Home + + ${raw(` + + `)} +
+ `; +}; + +export const renderAuthorizationApprovedContent = async (redirectUrl: string) => { + return renderApproveContent('Authorization approved!', 'success', redirectUrl); +}; + +export const renderAuthorizationRejectedContent = async (redirectUrl: string) => { + return renderApproveContent('Authorization rejected.', 'error', redirectUrl); +}; + +export const parseApproveFormBody = async ( + body: { + [x: string]: string | File; + }, + config: ServerConfig, +) => { + const parsedClientProps = Object.fromEntries( + config.clientProperties.map((prop: ClientProperty) => { + const rawValue = body[`clientopt_${prop.key}`]; + const value = prop.type === 'number' ? Number(rawValue) : rawValue; + return [prop.key, value]; + }), + ); + + const filters: Filter[] = []; + + if (body.read_only_operations === 'on') { + filters.push({ + type: 'operation', + op: 'exclude', + value: 'write', + }); + } + + // Parse advanced options + const clientConfig: McpOptions = { + client: (body.client as ClientType) || undefined, + includeDynamicTools: body.dynamic_tools === 'on', + includeAllTools: body.dynamic_tools !== 'on', + filters, + }; + + let oauthReqInfo: AuthRequest | null = null; + try { + oauthReqInfo = JSON.parse(body.oauthReqInfo as string) as AuthRequest; + if (Object.keys(oauthReqInfo).length === 0) { + oauthReqInfo = null; + } + } catch (e) { + oauthReqInfo = null; + } + + return { oauthReqInfo, clientProps: parsedClientProps, clientConfig, action: body.action }; +}; diff --git a/packages/mcp-server/cloudflare-worker/static/home.md b/packages/mcp-server/cloudflare-worker/static/home.md new file mode 100644 index 00000000..f40a97d0 --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/static/home.md @@ -0,0 +1,86 @@ +# ImageKit Remote MCP Server + +This MCP server is available at: + +``` +{{cloudflareWorkerUrl}} +``` + +### Claude.ai + +To connect Claude Web to this MCP server: + +1. Open Claude Web +2. Go to Settings -> Connectors +3. Click "+ Add Custom Connector" +4. Add the MCP server URL: `{{cloudflareWorkerUrl}}` + +### Claude Desktop + +Claude Desktop requires using the `mcp-remote` package to connect to remote MCP servers: + +1. Edit your Claude Desktop configuration file: + - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` + - Windows: `%APPDATA%\Claude\claude_desktop_config.json` +2. Add the following configuration: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "npx", + "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"] + } + } +} +``` + +3. Restart Claude Desktop to see the MCP server connection (look for the hammer icon) + +### Cursor + +1. Edit your Cursor MCP configuration file at `~/.cursor/mcp.json` +2. Add the following configuration: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "npx", + "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"] + } + } +} +``` + +### Windsurf + +1. Edit your Windsurf configuration file at `~/.codeium/windsurf/mcp_config.json` +2. Add the following configuration: + +```json +{ + "mcpServers": { + "imagekit_nodejs_api": { + "command": "npx", + "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"] + } + } +} +``` + +## Troubleshooting + +If you encounter issues connecting: + +1. Ensure you have Node.js 18 or higher installed +2. Try clearing MCP authentication cache: `rm -rf ~/.mcp-auth` +3. Restart your MCP client application +4. Check client logs for error messages + +## Learn More + +For more information about MCP: + +- [MCP Introduction](https://modelcontextprotocol.io/introduction) +- [mcp-remote package](https://www.npmjs.com/package/mcp-remote) diff --git a/packages/mcp-server/cloudflare-worker/tsconfig.json b/packages/mcp-server/cloudflare-worker/tsconfig.json new file mode 100644 index 00000000..3464dc29 --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": ["es2021"], + "jsx": "react-jsx", + "module": "es2022", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "noEmit": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["worker-configuration.d.ts", "src/**/*.ts"] +} diff --git a/packages/mcp-server/cloudflare-worker/worker-configuration.d.ts b/packages/mcp-server/cloudflare-worker/worker-configuration.d.ts new file mode 100644 index 00000000..813a10ff --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/worker-configuration.d.ts @@ -0,0 +1,5707 @@ +// Generated by Wrangler by running `wrangler types` (hash: 9c6c6f60ec3f74c7ee99dddce04c3506) +// Runtime types generated with workerd@1.20250317.0 2025-03-10 +declare namespace Cloudflare { + interface Env { + OAUTH_KV: KVNamespace; + MCP_OBJECT: DurableObjectNamespace; + ASSETS: Fetcher; + } +} +interface Env extends Cloudflare.Env {} + +// Begin runtime types +/*! ***************************************************************************** +Copyright (c) Cloudflare. All rights reserved. +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* eslint-disable */ +// noinspection JSUnusedGlobalSymbols +declare var onmessage: never; +/** + * An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException) + */ +declare class DOMException extends Error { + constructor(message?: string, name?: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) */ + readonly message: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) */ + readonly name: string; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code) + */ + readonly code: number; + static readonly INDEX_SIZE_ERR: number; + static readonly DOMSTRING_SIZE_ERR: number; + static readonly HIERARCHY_REQUEST_ERR: number; + static readonly WRONG_DOCUMENT_ERR: number; + static readonly INVALID_CHARACTER_ERR: number; + static readonly NO_DATA_ALLOWED_ERR: number; + static readonly NO_MODIFICATION_ALLOWED_ERR: number; + static readonly NOT_FOUND_ERR: number; + static readonly NOT_SUPPORTED_ERR: number; + static readonly INUSE_ATTRIBUTE_ERR: number; + static readonly INVALID_STATE_ERR: number; + static readonly SYNTAX_ERR: number; + static readonly INVALID_MODIFICATION_ERR: number; + static readonly NAMESPACE_ERR: number; + static readonly INVALID_ACCESS_ERR: number; + static readonly VALIDATION_ERR: number; + static readonly TYPE_MISMATCH_ERR: number; + static readonly SECURITY_ERR: number; + static readonly NETWORK_ERR: number; + static readonly ABORT_ERR: number; + static readonly URL_MISMATCH_ERR: number; + static readonly QUOTA_EXCEEDED_ERR: number; + static readonly TIMEOUT_ERR: number; + static readonly INVALID_NODE_TYPE_ERR: number; + static readonly DATA_CLONE_ERR: number; + get stack(): any; + set stack(value: any); +} +type WorkerGlobalScopeEventMap = { + fetch: FetchEvent; + scheduled: ScheduledEvent; + queue: QueueEvent; + unhandledrejection: PromiseRejectionEvent; + rejectionhandled: PromiseRejectionEvent; +}; +declare abstract class WorkerGlobalScope extends EventTarget { + EventTarget: typeof EventTarget; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */ +interface Console { + "assert"(condition?: boolean, ...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */ + clear(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */ + count(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countreset_static) */ + countReset(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */ + debug(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) */ + dir(item?: any, options?: any): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) */ + dirxml(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */ + error(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */ + group(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupcollapsed_static) */ + groupCollapsed(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupend_static) */ + groupEnd(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */ + info(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */ + log(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) */ + table(tabularData?: any, properties?: string[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */ + time(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeend_static) */ + timeEnd(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timelog_static) */ + timeLog(label?: string, ...data: any[]): void; + timeStamp(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */ + trace(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */ + warn(...data: any[]): void; +} +declare const console: Console; +type BufferSource = ArrayBufferView | ArrayBuffer; +type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; +declare namespace WebAssembly { + class CompileError extends Error { + constructor(message?: string); + } + class RuntimeError extends Error { + constructor(message?: string); + } + type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128"; + interface GlobalDescriptor { + value: ValueType; + mutable?: boolean; + } + class Global { + constructor(descriptor: GlobalDescriptor, value?: any); + value: any; + valueOf(): any; + } + type ImportValue = ExportValue | number; + type ModuleImports = Record; + type Imports = Record; + type ExportValue = Function | Global | Memory | Table; + type Exports = Record; + class Instance { + constructor(module: Module, imports?: Imports); + readonly exports: Exports; + } + interface MemoryDescriptor { + initial: number; + maximum?: number; + shared?: boolean; + } + class Memory { + constructor(descriptor: MemoryDescriptor); + readonly buffer: ArrayBuffer; + grow(delta: number): number; + } + type ImportExportKind = "function" | "global" | "memory" | "table"; + interface ModuleExportDescriptor { + kind: ImportExportKind; + name: string; + } + interface ModuleImportDescriptor { + kind: ImportExportKind; + module: string; + name: string; + } + abstract class Module { + static customSections(module: Module, sectionName: string): ArrayBuffer[]; + static exports(module: Module): ModuleExportDescriptor[]; + static imports(module: Module): ModuleImportDescriptor[]; + } + type TableKind = "anyfunc" | "externref"; + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + class Table { + constructor(descriptor: TableDescriptor, value?: any); + readonly length: number; + get(index: number): any; + grow(delta: number, value?: any): number; + set(index: number, value?: any): void; + } + function instantiate(module: Module, imports?: Imports): Promise; + function validate(bytes: BufferSource): boolean; +} +/** + * This ServiceWorker API interface represents the global execution context of a service worker. + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) + */ +interface ServiceWorkerGlobalScope extends WorkerGlobalScope { + DOMException: typeof DOMException; + WorkerGlobalScope: typeof WorkerGlobalScope; + btoa(data: string): string; + atob(data: string): string; + setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; + setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; + clearTimeout(timeoutId: number | null): void; + setInterval(callback: (...args: any[]) => void, msDelay?: number): number; + setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; + clearInterval(timeoutId: number | null): void; + queueMicrotask(task: Function): void; + structuredClone(value: T, options?: StructuredSerializeOptions): T; + reportError(error: any): void; + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; + self: ServiceWorkerGlobalScope; + crypto: Crypto; + caches: CacheStorage; + scheduler: Scheduler; + performance: Performance; + Cloudflare: Cloudflare; + readonly origin: string; + Event: typeof Event; + ExtendableEvent: typeof ExtendableEvent; + CustomEvent: typeof CustomEvent; + PromiseRejectionEvent: typeof PromiseRejectionEvent; + FetchEvent: typeof FetchEvent; + TailEvent: typeof TailEvent; + TraceEvent: typeof TailEvent; + ScheduledEvent: typeof ScheduledEvent; + MessageEvent: typeof MessageEvent; + CloseEvent: typeof CloseEvent; + ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader; + ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader; + ReadableStream: typeof ReadableStream; + WritableStream: typeof WritableStream; + WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter; + TransformStream: typeof TransformStream; + ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy; + CountQueuingStrategy: typeof CountQueuingStrategy; + ErrorEvent: typeof ErrorEvent; + EventSource: typeof EventSource; + ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest; + ReadableStreamDefaultController: typeof ReadableStreamDefaultController; + ReadableByteStreamController: typeof ReadableByteStreamController; + WritableStreamDefaultController: typeof WritableStreamDefaultController; + TransformStreamDefaultController: typeof TransformStreamDefaultController; + CompressionStream: typeof CompressionStream; + DecompressionStream: typeof DecompressionStream; + TextEncoderStream: typeof TextEncoderStream; + TextDecoderStream: typeof TextDecoderStream; + Headers: typeof Headers; + Body: typeof Body; + Request: typeof Request; + Response: typeof Response; + WebSocket: typeof WebSocket; + WebSocketPair: typeof WebSocketPair; + WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair; + AbortController: typeof AbortController; + AbortSignal: typeof AbortSignal; + TextDecoder: typeof TextDecoder; + TextEncoder: typeof TextEncoder; + navigator: Navigator; + Navigator: typeof Navigator; + URL: typeof URL; + URLSearchParams: typeof URLSearchParams; + URLPattern: typeof URLPattern; + Blob: typeof Blob; + File: typeof File; + FormData: typeof FormData; + Crypto: typeof Crypto; + SubtleCrypto: typeof SubtleCrypto; + CryptoKey: typeof CryptoKey; + CacheStorage: typeof CacheStorage; + Cache: typeof Cache; + FixedLengthStream: typeof FixedLengthStream; + IdentityTransformStream: typeof IdentityTransformStream; + HTMLRewriter: typeof HTMLRewriter; + GPUAdapter: typeof GPUAdapter; + GPUOutOfMemoryError: typeof GPUOutOfMemoryError; + GPUValidationError: typeof GPUValidationError; + GPUInternalError: typeof GPUInternalError; + GPUDeviceLostInfo: typeof GPUDeviceLostInfo; + GPUBufferUsage: typeof GPUBufferUsage; + GPUShaderStage: typeof GPUShaderStage; + GPUMapMode: typeof GPUMapMode; + GPUTextureUsage: typeof GPUTextureUsage; + GPUColorWrite: typeof GPUColorWrite; +} +declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; +declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; +/** + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) + */ +declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ +declare function btoa(data: string): string; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ +declare function atob(data: string): string; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */ +declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setTimeout) */ +declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearTimeout) */ +declare function clearTimeout(timeoutId: number | null): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */ +declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/setInterval) */ +declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/clearInterval) */ +declare function clearInterval(timeoutId: number | null): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/queueMicrotask) */ +declare function queueMicrotask(task: Function): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/structuredClone) */ +declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/reportError) */ +declare function reportError(error: any): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch) */ +declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise; +declare const self: ServiceWorkerGlobalScope; +/** +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ +declare const crypto: Crypto; +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare const caches: CacheStorage; +declare const scheduler: Scheduler; +/** +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ +declare const performance: Performance; +declare const Cloudflare: Cloudflare; +declare const origin: string; +declare const navigator: Navigator; +interface TestController { +} +interface ExecutionContext { + waitUntil(promise: Promise): void; + passThroughOnException(): void; + props: any; +} +type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; +type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; +type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; +interface ExportedHandler { + fetch?: ExportedHandlerFetchHandler; + tail?: ExportedHandlerTailHandler; + trace?: ExportedHandlerTraceHandler; + tailStream?: ExportedHandlerTailStreamHandler; + scheduled?: ExportedHandlerScheduledHandler; + test?: ExportedHandlerTestHandler; + email?: EmailExportedHandler; + queue?: ExportedHandlerQueueHandler; +} +interface StructuredSerializeOptions { + transfer?: any[]; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) */ +declare abstract class PromiseRejectionEvent extends Event { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) */ + readonly promise: Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) */ + readonly reason: any; +} +declare abstract class Navigator { + sendBeacon(url: string, body?: (ReadableStream | string | (ArrayBuffer | ArrayBufferView) | Blob | FormData | URLSearchParams | URLSearchParams)): boolean; + readonly userAgent: string; + readonly gpu?: GPU; +} +/** +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ +interface Performance { + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ + readonly timeOrigin: number; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ + now(): number; +} +interface AlarmInvocationInfo { + readonly isRetry: boolean; + readonly retryCount: number; +} +interface Cloudflare { + readonly compatibilityFlags: Record; +} +interface DurableObject { + fetch(request: Request): Response | Promise; + alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; + webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; + webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; + webSocketError?(ws: WebSocket, error: unknown): void | Promise; +} +type DurableObjectStub = Fetcher & { + readonly id: DurableObjectId; + readonly name?: string; +}; +interface DurableObjectId { + toString(): string; + equals(other: DurableObjectId): boolean; + readonly name?: string; +} +interface DurableObjectNamespace { + newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; + idFromName(name: string): DurableObjectId; + idFromString(id: string): DurableObjectId; + get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; + jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; +} +type DurableObjectJurisdiction = "eu" | "fedramp"; +interface DurableObjectNamespaceNewUniqueIdOptions { + jurisdiction?: DurableObjectJurisdiction; +} +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +interface DurableObjectNamespaceGetDurableObjectOptions { + locationHint?: DurableObjectLocationHint; +} +interface DurableObjectState { + waitUntil(promise: Promise): void; + readonly id: DurableObjectId; + readonly storage: DurableObjectStorage; + container?: Container; + blockConcurrencyWhile(callback: () => Promise): Promise; + acceptWebSocket(ws: WebSocket, tags?: string[]): void; + getWebSockets(tag?: string): WebSocket[]; + setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void; + getWebSocketAutoResponse(): WebSocketRequestResponsePair | null; + getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null; + setHibernatableWebSocketEventTimeout(timeoutMs?: number): void; + getHibernatableWebSocketEventTimeout(): number | null; + getTags(ws: WebSocket): string[]; + abort(reason?: string): void; +} +interface DurableObjectTransaction { + get(key: string, options?: DurableObjectGetOptions): Promise; + get(keys: string[], options?: DurableObjectGetOptions): Promise>; + list(options?: DurableObjectListOptions): Promise>; + put(key: string, value: T, options?: DurableObjectPutOptions): Promise; + put(entries: Record, options?: DurableObjectPutOptions): Promise; + delete(key: string, options?: DurableObjectPutOptions): Promise; + delete(keys: string[], options?: DurableObjectPutOptions): Promise; + rollback(): void; + getAlarm(options?: DurableObjectGetAlarmOptions): Promise; + setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; + deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; +} +interface DurableObjectStorage { + get(key: string, options?: DurableObjectGetOptions): Promise; + get(keys: string[], options?: DurableObjectGetOptions): Promise>; + list(options?: DurableObjectListOptions): Promise>; + put(key: string, value: T, options?: DurableObjectPutOptions): Promise; + put(entries: Record, options?: DurableObjectPutOptions): Promise; + delete(key: string, options?: DurableObjectPutOptions): Promise; + delete(keys: string[], options?: DurableObjectPutOptions): Promise; + deleteAll(options?: DurableObjectPutOptions): Promise; + transaction(closure: (txn: DurableObjectTransaction) => Promise): Promise; + getAlarm(options?: DurableObjectGetAlarmOptions): Promise; + setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; + deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; + sync(): Promise; + sql: SqlStorage; + transactionSync(closure: () => T): T; + getCurrentBookmark(): Promise; + getBookmarkForTime(timestamp: number | Date): Promise; + onNextSessionRestoreBookmark(bookmark: string): Promise; +} +interface DurableObjectListOptions { + start?: string; + startAfter?: string; + end?: string; + prefix?: string; + reverse?: boolean; + limit?: number; + allowConcurrency?: boolean; + noCache?: boolean; +} +interface DurableObjectGetOptions { + allowConcurrency?: boolean; + noCache?: boolean; +} +interface DurableObjectGetAlarmOptions { + allowConcurrency?: boolean; +} +interface DurableObjectPutOptions { + allowConcurrency?: boolean; + allowUnconfirmed?: boolean; + noCache?: boolean; +} +interface DurableObjectSetAlarmOptions { + allowConcurrency?: boolean; + allowUnconfirmed?: boolean; +} +declare class WebSocketRequestResponsePair { + constructor(request: string, response: string); + get request(): string; + get response(): string; +} +interface AnalyticsEngineDataset { + writeDataPoint(event?: AnalyticsEngineDataPoint): void; +} +interface AnalyticsEngineDataPoint { + indexes?: ((ArrayBuffer | string) | null)[]; + doubles?: number[]; + blobs?: ((ArrayBuffer | string) | null)[]; +} +/** + * An event which takes place in the DOM. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event) + */ +declare class Event { + constructor(type: string, init?: EventInit); + /** + * Returns the type of event, e.g. "click", "hashchange", or "submit". + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type) + */ + get type(): string; + /** + * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase) + */ + get eventPhase(): number; + /** + * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed) + */ + get composed(): boolean; + /** + * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles) + */ + get bubbles(): boolean; + /** + * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable) + */ + get cancelable(): boolean; + /** + * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented) + */ + get defaultPrevented(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue) + */ + get returnValue(): boolean; + /** + * Returns the object whose event listener's callback is currently being invoked. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget) + */ + get currentTarget(): EventTarget | undefined; + /** + * Returns the object to which event is dispatched (its target). + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target) + */ + get target(): EventTarget | undefined; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement) + */ + get srcElement(): EventTarget | undefined; + /** + * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp) + */ + get timeStamp(): number; + /** + * Returns true if event was dispatched by the user agent, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted) + */ + get isTrusted(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) + */ + get cancelBubble(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) + */ + set cancelBubble(value: boolean); + /** + * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation) + */ + stopImmediatePropagation(): void; + /** + * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) + */ + preventDefault(): void; + /** + * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) + */ + stopPropagation(): void; + /** + * Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath) + */ + composedPath(): EventTarget[]; + static readonly NONE: number; + static readonly CAPTURING_PHASE: number; + static readonly AT_TARGET: number; + static readonly BUBBLING_PHASE: number; +} +interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} +type EventListener = (event: EventType) => void; +interface EventListenerObject { + handleEvent(event: EventType): void; +} +type EventListenerOrEventListenerObject = EventListener | EventListenerObject; +/** + * EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget) + */ +declare class EventTarget = Record> { + constructor(); + /** + * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. + * + * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. + * + * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. + * + * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. + * + * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. + * + * If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted. + * + * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) + */ + addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; + /** + * Removes the event listener in target's event listener list with the same type, callback, and options. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener) + */ + removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; + /** + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) + */ + dispatchEvent(event: EventMap[keyof EventMap]): boolean; +} +interface EventTargetEventListenerOptions { + capture?: boolean; +} +interface EventTargetAddEventListenerOptions { + capture?: boolean; + passive?: boolean; + once?: boolean; + signal?: AbortSignal; +} +interface EventTargetHandlerObject { + handleEvent: (event: Event) => any | undefined; +} +/** + * A controller object that allows you to abort one or more DOM requests as and when desired. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController) + */ +declare class AbortController { + constructor(); + /** + * Returns the AbortSignal object associated with this object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal) + */ + get signal(): AbortSignal; + /** + * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort) + */ + abort(reason?: any): void; +} +/** + * A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal) + */ +declare abstract class AbortSignal extends EventTarget { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) */ + static abort(reason?: any): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) */ + static timeout(delay: number): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) */ + static any(signals: AbortSignal[]): AbortSignal; + /** + * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted) + */ + get aborted(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) */ + get reason(): any; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ + get onabort(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ + set onabort(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) */ + throwIfAborted(): void; +} +interface Scheduler { + wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise; +} +interface SchedulerWaitOptions { + signal?: AbortSignal; +} +/** + * Extends the lifetime of the install and activate events dispatched on the global scope as part of the service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it upgrades database schemas and deletes the outdated cache entries. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent) + */ +declare abstract class ExtendableEvent extends Event { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) */ + waitUntil(promise: Promise): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) */ +declare class CustomEvent extends Event { + constructor(type: string, init?: CustomEventCustomEventInit); + /** + * Returns any custom data event was created with. Typically used for synthetic events. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail) + */ + get detail(): T; +} +interface CustomEventCustomEventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; + detail?: any; +} +/** + * A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) + */ +declare class Blob { + constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + get size(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + get type(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number, type?: string): Blob; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) */ + arrayBuffer(): Promise; + bytes(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) */ + stream(): ReadableStream; +} +interface BlobOptions { + type?: string; +} +/** + * Provides information about files and allows JavaScript in a web page to access their content. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File) + */ +declare class File extends Blob { + constructor(bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined, name: string, options?: FileOptions); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + get name(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + get lastModified(): number; +} +interface FileOptions { + type?: string; + lastModified?: number; +} +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare abstract class CacheStorage { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) */ + open(cacheName: string): Promise; + readonly default: Cache; +} +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare abstract class Cache { + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ + delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */ + match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */ + put(request: RequestInfo | URL, response: Response): Promise; +} +interface CacheQueryOptions { + ignoreMethod?: boolean; +} +/** +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ +declare abstract class Crypto { + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) + */ + get subtle(): SubtleCrypto; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) */ + getRandomValues(buffer: T): T; + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) + */ + randomUUID(): string; + DigestStream: typeof DigestStream; +} +/** + * This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto). + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) + */ +declare abstract class SubtleCrypto { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */ + encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, plainText: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) */ + decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, cipherText: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) */ + sign(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */ + verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */ + digest(algorithm: string | SubtleCryptoHashAlgorithm, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ + generateKey(algorithm: string | SubtleCryptoGenerateKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */ + deriveKey(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */ + deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, length?: number | null): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) */ + importKey(format: string, keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */ + exportKey(format: string, key: CryptoKey): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ + wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */ + unwrapKey(format: string, wrappedKey: ArrayBuffer | ArrayBufferView, unwrappingKey: CryptoKey, unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean; +} +/** + * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey) + */ +declare abstract class CryptoKey { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) */ + readonly type: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) */ + readonly extractable: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) */ + readonly algorithm: CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyArbitraryKeyAlgorithm; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) */ + readonly usages: string[]; +} +interface CryptoKeyPair { + publicKey: CryptoKey; + privateKey: CryptoKey; +} +interface JsonWebKey { + kty: string; + use?: string; + key_ops?: string[]; + alg?: string; + ext?: boolean; + crv?: string; + x?: string; + y?: string; + d?: string; + n?: string; + e?: string; + p?: string; + q?: string; + dp?: string; + dq?: string; + qi?: string; + oth?: RsaOtherPrimesInfo[]; + k?: string; +} +interface RsaOtherPrimesInfo { + r?: string; + d?: string; + t?: string; +} +interface SubtleCryptoDeriveKeyAlgorithm { + name: string; + salt?: ArrayBuffer; + iterations?: number; + hash?: (string | SubtleCryptoHashAlgorithm); + $public?: CryptoKey; + info?: ArrayBuffer; +} +interface SubtleCryptoEncryptAlgorithm { + name: string; + iv?: ArrayBuffer; + additionalData?: ArrayBuffer; + tagLength?: number; + counter?: ArrayBuffer; + length?: number; + label?: ArrayBuffer; +} +interface SubtleCryptoGenerateKeyAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + modulusLength?: number; + publicExponent?: ArrayBuffer; + length?: number; + namedCurve?: string; +} +interface SubtleCryptoHashAlgorithm { + name: string; +} +interface SubtleCryptoImportKeyAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + length?: number; + namedCurve?: string; + compressed?: boolean; +} +interface SubtleCryptoSignAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + dataLength?: number; + saltLength?: number; +} +interface CryptoKeyKeyAlgorithm { + name: string; +} +interface CryptoKeyAesKeyAlgorithm { + name: string; + length: number; +} +interface CryptoKeyHmacKeyAlgorithm { + name: string; + hash: CryptoKeyKeyAlgorithm; + length: number; +} +interface CryptoKeyRsaKeyAlgorithm { + name: string; + modulusLength: number; + publicExponent: ArrayBuffer | (ArrayBuffer | ArrayBufferView); + hash?: CryptoKeyKeyAlgorithm; +} +interface CryptoKeyEllipticKeyAlgorithm { + name: string; + namedCurve: string; +} +interface CryptoKeyArbitraryKeyAlgorithm { + name: string; + hash?: CryptoKeyKeyAlgorithm; + namedCurve?: string; + length?: number; +} +declare class DigestStream extends WritableStream { + constructor(algorithm: string | SubtleCryptoHashAlgorithm); + get digest(): Promise; + get bytesWritten(): number | bigint; +} +/** + * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) + */ +declare class TextDecoder { + constructor(decoder?: string, options?: TextDecoderConstructorOptions); + /** + * Returns the result of running encoding's decoder. The method can be invoked zero or more times with options's stream set to true, and then once without options's stream (or set to false), to process a fragmented input. If the invocation without options's stream (or set to false) has no input, it's clearest to omit both arguments. + * + * ``` + * var string = "", decoder = new TextDecoder(encoding), buffer; + * while(buffer = next_chunk()) { + * string += decoder.decode(buffer, {stream:true}); + * } + * string += decoder.decode(); // end-of-queue + * ``` + * + * If the error mode is "fatal" and encoding's decoder returns error, throws a TypeError. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) + */ + decode(input?: (ArrayBuffer | ArrayBufferView), options?: TextDecoderDecodeOptions): string; + get encoding(): string; + get fatal(): boolean; + get ignoreBOM(): boolean; +} +/** + * TextEncoder takes a stream of code points as input and emits a stream of bytes. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder) + */ +declare class TextEncoder { + constructor(); + /** + * Returns the result of running UTF-8's encoder. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode) + */ + encode(input?: string): Uint8Array; + /** + * Runs the UTF-8 encoder on source, stores the result of that operation into destination, and returns the progress made as an object wherein read is the number of converted code units of source and written is the number of bytes modified in destination. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto) + */ + encodeInto(input: string, buffer: ArrayBuffer | ArrayBufferView): TextEncoderEncodeIntoResult; + get encoding(): string; +} +interface TextDecoderConstructorOptions { + fatal: boolean; + ignoreBOM: boolean; +} +interface TextDecoderDecodeOptions { + stream: boolean; +} +interface TextEncoderEncodeIntoResult { + read: number; + written: number; +} +/** + * Events providing information related to errors in scripts or in files. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent) + */ +declare class ErrorEvent extends Event { + constructor(type: string, init?: ErrorEventErrorEventInit); + get filename(): string; + get message(): string; + get lineno(): number; + get colno(): number; + get error(): any; +} +interface ErrorEventErrorEventInit { + message?: string; + filename?: string; + lineno?: number; + colno?: number; + error?: any; +} +/** + * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data". + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData) + */ +declare class FormData { + constructor(); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ + append(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ + append(name: string, value: Blob, filename?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) */ + delete(name: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) */ + get(name: string): (File | string) | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) */ + getAll(name: string): (File | string)[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */ + has(name: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ + set(name: string, value: Blob, filename?: string): void; + /* Returns an array of key, value pairs for every entry in the list. */ + entries(): IterableIterator<[ + key: string, + value: File | string + ]>; + /* Returns a list of keys in the list. */ + keys(): IterableIterator; + /* Returns a list of values in the list. */ + values(): IterableIterator<(File | string)>; + forEach(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: File | string + ]>; +} +interface ContentOptions { + html?: boolean; +} +declare class HTMLRewriter { + constructor(); + on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter; + onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter; + transform(response: Response): Response; +} +interface HTMLRewriterElementContentHandlers { + element?(element: Element): void | Promise; + comments?(comment: Comment): void | Promise; + text?(element: Text): void | Promise; +} +interface HTMLRewriterDocumentContentHandlers { + doctype?(doctype: Doctype): void | Promise; + comments?(comment: Comment): void | Promise; + text?(text: Text): void | Promise; + end?(end: DocumentEnd): void | Promise; +} +interface Doctype { + readonly name: string | null; + readonly publicId: string | null; + readonly systemId: string | null; +} +interface Element { + tagName: string; + readonly attributes: IterableIterator; + readonly removed: boolean; + readonly namespaceURI: string; + getAttribute(name: string): string | null; + hasAttribute(name: string): boolean; + setAttribute(name: string, value: string): Element; + removeAttribute(name: string): Element; + before(content: string | ReadableStream | Response, options?: ContentOptions): Element; + after(content: string | ReadableStream | Response, options?: ContentOptions): Element; + prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element; + append(content: string | ReadableStream | Response, options?: ContentOptions): Element; + replace(content: string | ReadableStream | Response, options?: ContentOptions): Element; + remove(): Element; + removeAndKeepContent(): Element; + setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element; + onEndTag(handler: (tag: EndTag) => void | Promise): void; +} +interface EndTag { + name: string; + before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; + after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; + remove(): EndTag; +} +interface Comment { + text: string; + readonly removed: boolean; + before(content: string, options?: ContentOptions): Comment; + after(content: string, options?: ContentOptions): Comment; + replace(content: string, options?: ContentOptions): Comment; + remove(): Comment; +} +interface Text { + readonly text: string; + readonly lastInTextNode: boolean; + readonly removed: boolean; + before(content: string | ReadableStream | Response, options?: ContentOptions): Text; + after(content: string | ReadableStream | Response, options?: ContentOptions): Text; + replace(content: string | ReadableStream | Response, options?: ContentOptions): Text; + remove(): Text; +} +interface DocumentEnd { + append(content: string, options?: ContentOptions): DocumentEnd; +} +/** + * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) + */ +declare abstract class FetchEvent extends ExtendableEvent { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) */ + readonly request: Request; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */ + respondWith(promise: Response | Promise): void; + passThroughOnException(): void; +} +type HeadersInit = Headers | Iterable> | Record; +/** + * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.  You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) + */ +declare class Headers { + constructor(init?: HeadersInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) */ + get(name: string): string | null; + getAll(name: string): string[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) */ + getSetCookie(): string[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) */ + has(name: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) */ + append(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) */ + delete(name: string): void; + forEach(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; + /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ + entries(): IterableIterator<[ + key: string, + value: string + ]>; + /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ + keys(): IterableIterator; + /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; +} +type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData; +declare abstract class Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */ + get body(): ReadableStream | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ + get bodyUsed(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ + arrayBuffer(): Promise; + bytes(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ + text(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ + json(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */ + formData(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ + blob(): Promise; +} +/** + * This Fetch API interface represents the response to a request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) + */ +declare var Response: { + prototype: Response; + new (body?: BodyInit | null, init?: ResponseInit): Response; + error(): Response; + redirect(url: string, status?: number): Response; + json(any: any, maybeInit?: (ResponseInit | Response)): Response; +}; +/** + * This Fetch API interface represents the response to a request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) + */ +interface Response extends Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) */ + clone(): Response; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) */ + status: number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) */ + statusText: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */ + headers: Headers; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) */ + ok: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) */ + redirected: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) */ + url: string; + webSocket: WebSocket | null; + cf: any | undefined; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) */ + type: "default" | "error"; +} +interface ResponseInit { + status?: number; + statusText?: string; + headers?: HeadersInit; + cf?: any; + webSocket?: (WebSocket | null); + encodeBody?: "automatic" | "manual"; +} +type RequestInfo> = Request | string; +/** + * This Fetch API interface represents a resource request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) + */ +declare var Request: { + prototype: Request; + new >(input: RequestInfo | URL, init?: RequestInit): Request; +}; +/** + * This Fetch API interface represents a resource request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) + */ +interface Request> extends Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) */ + clone(): Request; + /** + * Returns request's HTTP method, which is "GET" by default. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method) + */ + method: string; + /** + * Returns the URL of request as a string. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url) + */ + url: string; + /** + * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers) + */ + headers: Headers; + /** + * Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect) + */ + redirect: string; + fetcher: Fetcher | null; + /** + * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) + */ + signal: AbortSignal; + cf: Cf | undefined; + /** + * Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) + */ + integrity: string; + /* Returns a boolean indicating whether or not request can outlive the global in which it was created. */ + keepalive: boolean; + /** + * Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache) + */ + cache?: "no-store"; +} +interface RequestInit { + /* A string to set request's method. */ + method?: string; + /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ + headers?: HeadersInit; + /* A BodyInit object or null to set request's body. */ + body?: BodyInit | null; + /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ + redirect?: string; + fetcher?: (Fetcher | null); + cf?: Cf; + /* A string indicating how the request will interact with the browser's cache to set request's cache. */ + cache?: "no-store"; + /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ + integrity?: string; + /* An AbortSignal to set request's signal. */ + signal?: (AbortSignal | null); + encodeResponseBody?: "automatic" | "manual"; +} +type Service = Fetcher; +type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; + connect(address: SocketAddress | string, options?: SocketOptions): Socket; +}; +interface KVNamespaceListKey { + name: Key; + expiration?: number; + metadata?: Metadata; +} +type KVNamespaceListResult = { + list_complete: false; + keys: KVNamespaceListKey[]; + cursor: string; + cacheStatus: string | null; +} | { + list_complete: true; + keys: KVNamespaceListKey[]; + cacheStatus: string | null; +}; +interface KVNamespace { + get(key: Key, options?: Partial>): Promise; + get(key: Key, type: "text"): Promise; + get(key: Key, type: "json"): Promise; + get(key: Key, type: "arrayBuffer"): Promise; + get(key: Key, type: "stream"): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; + list(options?: KVNamespaceListOptions): Promise>; + put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; + getWithMetadata(key: Key, options?: Partial>): Promise>; + getWithMetadata(key: Key, type: "text"): Promise>; + getWithMetadata(key: Key, type: "json"): Promise>; + getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; + getWithMetadata(key: Key, type: "stream"): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; + delete(key: Key): Promise; +} +interface KVNamespaceListOptions { + limit?: number; + prefix?: (string | null); + cursor?: (string | null); +} +interface KVNamespaceGetOptions { + type: Type; + cacheTtl?: number; +} +interface KVNamespacePutOptions { + expiration?: number; + expirationTtl?: number; + metadata?: (any | null); +} +interface KVNamespaceGetWithMetadataResult { + value: Value | null; + metadata: Metadata | null; + cacheStatus: string | null; +} +type QueueContentType = "text" | "bytes" | "json" | "v8"; +interface Queue { + send(message: Body, options?: QueueSendOptions): Promise; + sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; +} +interface QueueSendOptions { + contentType?: QueueContentType; + delaySeconds?: number; +} +interface QueueSendBatchOptions { + delaySeconds?: number; +} +interface MessageSendRequest { + body: Body; + contentType?: QueueContentType; + delaySeconds?: number; +} +interface QueueRetryOptions { + delaySeconds?: number; +} +interface Message { + readonly id: string; + readonly timestamp: Date; + readonly body: Body; + readonly attempts: number; + retry(options?: QueueRetryOptions): void; + ack(): void; +} +interface QueueEvent extends ExtendableEvent { + readonly messages: readonly Message[]; + readonly queue: string; + retryAll(options?: QueueRetryOptions): void; + ackAll(): void; +} +interface MessageBatch { + readonly messages: readonly Message[]; + readonly queue: string; + retryAll(options?: QueueRetryOptions): void; + ackAll(): void; +} +interface R2Error extends Error { + readonly name: string; + readonly code: number; + readonly message: string; + readonly action: string; + readonly stack: any; +} +interface R2ListOptions { + limit?: number; + prefix?: string; + cursor?: string; + delimiter?: string; + startAfter?: string; + include?: ("httpMetadata" | "customMetadata")[]; +} +declare abstract class R2Bucket { + head(key: string): Promise; + get(key: string, options: R2GetOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; + get(key: string, options?: R2GetOptions): Promise; + put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; + put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise; + createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; + resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; + delete(keys: string | string[]): Promise; + list(options?: R2ListOptions): Promise; +} +interface R2MultipartUpload { + readonly key: string; + readonly uploadId: string; + uploadPart(partNumber: number, value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob, options?: R2UploadPartOptions): Promise; + abort(): Promise; + complete(uploadedParts: R2UploadedPart[]): Promise; +} +interface R2UploadedPart { + partNumber: number; + etag: string; +} +declare abstract class R2Object { + readonly key: string; + readonly version: string; + readonly size: number; + readonly etag: string; + readonly httpEtag: string; + readonly checksums: R2Checksums; + readonly uploaded: Date; + readonly httpMetadata?: R2HTTPMetadata; + readonly customMetadata?: Record; + readonly range?: R2Range; + readonly storageClass: string; + readonly ssecKeyMd5?: string; + writeHttpMetadata(headers: Headers): void; +} +interface R2ObjectBody extends R2Object { + get body(): ReadableStream; + get bodyUsed(): boolean; + arrayBuffer(): Promise; + text(): Promise; + json(): Promise; + blob(): Promise; +} +type R2Range = { + offset: number; + length?: number; +} | { + offset?: number; + length: number; +} | { + suffix: number; +}; +interface R2Conditional { + etagMatches?: string; + etagDoesNotMatch?: string; + uploadedBefore?: Date; + uploadedAfter?: Date; + secondsGranularity?: boolean; +} +interface R2GetOptions { + onlyIf?: (R2Conditional | Headers); + range?: (R2Range | Headers); + ssecKey?: (ArrayBuffer | string); +} +interface R2PutOptions { + onlyIf?: (R2Conditional | Headers); + httpMetadata?: (R2HTTPMetadata | Headers); + customMetadata?: Record; + md5?: (ArrayBuffer | string); + sha1?: (ArrayBuffer | string); + sha256?: (ArrayBuffer | string); + sha384?: (ArrayBuffer | string); + sha512?: (ArrayBuffer | string); + storageClass?: string; + ssecKey?: (ArrayBuffer | string); +} +interface R2MultipartOptions { + httpMetadata?: (R2HTTPMetadata | Headers); + customMetadata?: Record; + storageClass?: string; + ssecKey?: (ArrayBuffer | string); +} +interface R2Checksums { + readonly md5?: ArrayBuffer; + readonly sha1?: ArrayBuffer; + readonly sha256?: ArrayBuffer; + readonly sha384?: ArrayBuffer; + readonly sha512?: ArrayBuffer; + toJSON(): R2StringChecksums; +} +interface R2StringChecksums { + md5?: string; + sha1?: string; + sha256?: string; + sha384?: string; + sha512?: string; +} +interface R2HTTPMetadata { + contentType?: string; + contentLanguage?: string; + contentDisposition?: string; + contentEncoding?: string; + cacheControl?: string; + cacheExpiry?: Date; +} +type R2Objects = { + objects: R2Object[]; + delimitedPrefixes: string[]; +} & ({ + truncated: true; + cursor: string; +} | { + truncated: false; +}); +interface R2UploadPartOptions { + ssecKey?: (ArrayBuffer | string); +} +declare abstract class ScheduledEvent extends ExtendableEvent { + readonly scheduledTime: number; + readonly cron: string; + noRetry(): void; +} +interface ScheduledController { + readonly scheduledTime: number; + readonly cron: string; + noRetry(): void; +} +interface QueuingStrategy { + highWaterMark?: (number | bigint); + size?: (chunk: T) => number | bigint; +} +interface UnderlyingSink { + type?: string; + start?: (controller: WritableStreamDefaultController) => void | Promise; + write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise; + abort?: (reason: any) => void | Promise; + close?: () => void | Promise; +} +interface UnderlyingByteSource { + type: "bytes"; + autoAllocateChunkSize?: number; + start?: (controller: ReadableByteStreamController) => void | Promise; + pull?: (controller: ReadableByteStreamController) => void | Promise; + cancel?: (reason: any) => void | Promise; +} +interface UnderlyingSource { + type?: "" | undefined; + start?: (controller: ReadableStreamDefaultController) => void | Promise; + pull?: (controller: ReadableStreamDefaultController) => void | Promise; + cancel?: (reason: any) => void | Promise; + expectedLength?: (number | bigint); +} +interface Transformer { + readableType?: string; + writableType?: string; + start?: (controller: TransformStreamDefaultController) => void | Promise; + transform?: (chunk: I, controller: TransformStreamDefaultController) => void | Promise; + flush?: (controller: TransformStreamDefaultController) => void | Promise; + cancel?: (reason: any) => void | Promise; + expectedLength?: number; +} +interface StreamPipeOptions { + /** + * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. + * + * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. + * + * Errors and closures of the source and destination streams propagate as follows: + * + * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination. + * + * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source. + * + * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error. + * + * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source. + * + * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. + */ + preventClose?: boolean; + preventAbort?: boolean; + preventCancel?: boolean; + signal?: AbortSignal; +} +type ReadableStreamReadResult = { + done: false; + value: R; +} | { + done: true; + value?: undefined; +}; +/** + * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) + */ +interface ReadableStream { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) */ + get locked(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) */ + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ + getReader(): ReadableStreamDefaultReader; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ + getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) */ + pipeThrough(transform: ReadableWritablePair, options?: StreamPipeOptions): ReadableStream; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) */ + pipeTo(destination: WritableStream, options?: StreamPipeOptions): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) */ + tee(): [ + ReadableStream, + ReadableStream + ]; + values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; + [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; +} +/** + * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) + */ +declare const ReadableStream: { + prototype: ReadableStream; + new (underlyingSource: UnderlyingByteSource, strategy?: QueuingStrategy): ReadableStream; + new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; +}; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) */ +declare class ReadableStreamDefaultReader { + constructor(stream: ReadableStream); + get closed(): Promise; + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) */ + read(): Promise>; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) */ + releaseLock(): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */ +declare class ReadableStreamBYOBReader { + constructor(stream: ReadableStream); + get closed(): Promise; + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) */ + read(view: T): Promise>; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) */ + releaseLock(): void; + readAtLeast(minElements: number, view: T): Promise>; +} +interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions { + min?: number; +} +interface ReadableStreamGetReaderOptions { + /** + * Creates a ReadableStreamBYOBReader and locks the stream to the new reader. + * + * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation. + */ + mode: "byob"; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) */ +declare abstract class ReadableStreamBYOBRequest { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) */ + get view(): Uint8Array | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) */ + respond(bytesWritten: number): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) */ + respondWithNewView(view: ArrayBuffer | ArrayBufferView): void; + get atLeast(): number | null; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) */ +declare abstract class ReadableStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) */ + close(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) */ + enqueue(chunk?: R): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) */ + error(reason: any): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) */ +declare abstract class ReadableByteStreamController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) */ + get byobRequest(): ReadableStreamBYOBRequest | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) */ + close(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) */ + enqueue(chunk: ArrayBuffer | ArrayBufferView): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) */ + error(reason: any): void; +} +/** + * This Streams API interface represents a controller allowing control of a WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController) + */ +declare abstract class WritableStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) */ + get signal(): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) */ + error(reason?: any): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) */ +declare abstract class TransformStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) */ + enqueue(chunk?: O): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) */ + error(reason: any): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) */ + terminate(): void; +} +interface ReadableWritablePair { + /** + * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. + * + * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. + */ + writable: WritableStream; + readable: ReadableStream; +} +/** + * This Streams API interface provides a standard abstraction for writing streaming data to a destination, known as a sink. This object comes with built-in backpressure and queuing. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream) + */ +declare class WritableStream { + constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) */ + get locked(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) */ + abort(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) */ + close(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) */ + getWriter(): WritableStreamDefaultWriter; +} +/** + * This Streams API interface is the object returned by WritableStream.getWriter() and once created locks the < writer to the WritableStream ensuring that no other streams can write to the underlying sink. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) + */ +declare class WritableStreamDefaultWriter { + constructor(stream: WritableStream); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) */ + get closed(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) */ + get ready(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) */ + abort(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) */ + close(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) */ + write(chunk?: W): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) */ + releaseLock(): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) */ +declare class TransformStream { + constructor(transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) */ + get readable(): ReadableStream; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) */ + get writable(): WritableStream; +} +declare class FixedLengthStream extends IdentityTransformStream { + constructor(expectedLength: number | bigint, queuingStrategy?: IdentityTransformStreamQueuingStrategy); +} +declare class IdentityTransformStream extends TransformStream { + constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy); +} +interface IdentityTransformStreamQueuingStrategy { + highWaterMark?: (number | bigint); +} +interface ReadableStreamValuesOptions { + preventCancel?: boolean; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) */ +declare class CompressionStream extends TransformStream { + constructor(format: "gzip" | "deflate" | "deflate-raw"); +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) */ +declare class DecompressionStream extends TransformStream { + constructor(format: "gzip" | "deflate" | "deflate-raw"); +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) */ +declare class TextEncoderStream extends TransformStream { + constructor(); + get encoding(): string; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) */ +declare class TextDecoderStream extends TransformStream { + constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit); + get encoding(): string; + get fatal(): boolean; + get ignoreBOM(): boolean; +} +interface TextDecoderStreamTextDecoderStreamInit { + fatal?: boolean; + ignoreBOM?: boolean; +} +/** + * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy) + */ +declare class ByteLengthQueuingStrategy implements QueuingStrategy { + constructor(init: QueuingStrategyInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) */ + get highWaterMark(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */ + get size(): (chunk?: any) => number; +} +/** + * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy) + */ +declare class CountQueuingStrategy implements QueuingStrategy { + constructor(init: QueuingStrategyInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) */ + get highWaterMark(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */ + get size(): (chunk?: any) => number; +} +interface QueuingStrategyInit { + /** + * Creates a new ByteLengthQueuingStrategy with the provided high water mark. + * + * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw. + */ + highWaterMark: number; +} +interface ScriptVersion { + id?: string; + tag?: string; + message?: string; +} +declare abstract class TailEvent extends ExtendableEvent { + readonly events: TraceItem[]; + readonly traces: TraceItem[]; +} +interface TraceItem { + readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; + readonly eventTimestamp: number | null; + readonly logs: TraceLog[]; + readonly exceptions: TraceException[]; + readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[]; + readonly scriptName: string | null; + readonly entrypoint?: string; + readonly scriptVersion?: ScriptVersion; + readonly dispatchNamespace?: string; + readonly scriptTags?: string[]; + readonly outcome: string; + readonly executionModel: string; + readonly truncated: boolean; + readonly cpuTime: number; + readonly wallTime: number; +} +interface TraceItemAlarmEventInfo { + readonly scheduledTime: Date; +} +interface TraceItemCustomEventInfo { +} +interface TraceItemScheduledEventInfo { + readonly scheduledTime: number; + readonly cron: string; +} +interface TraceItemQueueEventInfo { + readonly queue: string; + readonly batchSize: number; +} +interface TraceItemEmailEventInfo { + readonly mailFrom: string; + readonly rcptTo: string; + readonly rawSize: number; +} +interface TraceItemTailEventInfo { + readonly consumedEvents: TraceItemTailEventInfoTailItem[]; +} +interface TraceItemTailEventInfoTailItem { + readonly scriptName: string | null; +} +interface TraceItemFetchEventInfo { + readonly response?: TraceItemFetchEventInfoResponse; + readonly request: TraceItemFetchEventInfoRequest; +} +interface TraceItemFetchEventInfoRequest { + readonly cf?: any; + readonly headers: Record; + readonly method: string; + readonly url: string; + getUnredacted(): TraceItemFetchEventInfoRequest; +} +interface TraceItemFetchEventInfoResponse { + readonly status: number; +} +interface TraceItemJsRpcEventInfo { + readonly rpcMethod: string; +} +interface TraceItemHibernatableWebSocketEventInfo { + readonly getWebSocketEvent: TraceItemHibernatableWebSocketEventInfoMessage | TraceItemHibernatableWebSocketEventInfoClose | TraceItemHibernatableWebSocketEventInfoError; +} +interface TraceItemHibernatableWebSocketEventInfoMessage { + readonly webSocketEventType: string; +} +interface TraceItemHibernatableWebSocketEventInfoClose { + readonly webSocketEventType: string; + readonly code: number; + readonly wasClean: boolean; +} +interface TraceItemHibernatableWebSocketEventInfoError { + readonly webSocketEventType: string; +} +interface TraceLog { + readonly timestamp: number; + readonly level: string; + readonly message: any; +} +interface TraceException { + readonly timestamp: number; + readonly message: string; + readonly name: string; + readonly stack?: string; +} +interface TraceDiagnosticChannelEvent { + readonly timestamp: number; + readonly channel: string; + readonly message: any; +} +interface TraceMetrics { + readonly cpuTime: number; + readonly wallTime: number; +} +interface UnsafeTraceMetrics { + fromTrace(item: TraceItem): TraceMetrics; +} +/** + * The URL interface represents an object providing static methods used for creating object URLs. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) + */ +declare class URL { + constructor(url: string | URL, base?: string | URL); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) */ + get origin(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ + get href(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ + set href(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ + get protocol(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ + set protocol(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ + get username(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ + set username(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ + get password(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ + set password(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ + get host(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ + set host(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ + get hostname(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ + set hostname(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ + get port(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ + set port(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ + get pathname(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ + set pathname(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ + get search(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ + set search(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ + get hash(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ + set hash(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) */ + get searchParams(): URLSearchParams; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) */ + toJSON(): string; + /*function toString() { [native code] }*/ + toString(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) */ + static canParse(url: string, base?: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) */ + static parse(url: string, base?: string): URL | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) */ + static createObjectURL(object: File | Blob): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) */ + static revokeObjectURL(object_url: string): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) */ +declare class URLSearchParams { + constructor(init?: (Iterable> | Record | string)); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) */ + get size(): number; + /** + * Appends a specified key/value pair as a new search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append) + */ + append(name: string, value: string): void; + /** + * Deletes the given search parameter, and its associated value, from the list of all search parameters. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete) + */ + delete(name: string, value?: string): void; + /** + * Returns the first value associated to the given search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) + */ + get(name: string): string | null; + /** + * Returns all the values association with a given search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) + */ + getAll(name: string): string[]; + /** + * Returns a Boolean indicating if such a search parameter exists. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) + */ + has(name: string, value?: string): boolean; + /** + * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set) + */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) */ + sort(): void; + /* Returns an array of key, value pairs for every entry in the search params. */ + entries(): IterableIterator<[ + key: string, + value: string + ]>; + /* Returns a list of keys in the search params. */ + keys(): IterableIterator; + /* Returns a list of values in the search params. */ + values(): IterableIterator; + forEach(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void; + /*function toString() { [native code] } Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */ + toString(): string; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; +} +declare class URLPattern { + constructor(input?: (string | URLPatternURLPatternInit), baseURL?: (string | URLPatternURLPatternOptions), patternOptions?: URLPatternURLPatternOptions); + get protocol(): string; + get username(): string; + get password(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get hash(): string; + test(input?: (string | URLPatternURLPatternInit), baseURL?: string): boolean; + exec(input?: (string | URLPatternURLPatternInit), baseURL?: string): URLPatternURLPatternResult | null; +} +interface URLPatternURLPatternInit { + protocol?: string; + username?: string; + password?: string; + hostname?: string; + port?: string; + pathname?: string; + search?: string; + hash?: string; + baseURL?: string; +} +interface URLPatternURLPatternComponentResult { + input: string; + groups: Record; +} +interface URLPatternURLPatternResult { + inputs: (string | URLPatternURLPatternInit)[]; + protocol: URLPatternURLPatternComponentResult; + username: URLPatternURLPatternComponentResult; + password: URLPatternURLPatternComponentResult; + hostname: URLPatternURLPatternComponentResult; + port: URLPatternURLPatternComponentResult; + pathname: URLPatternURLPatternComponentResult; + search: URLPatternURLPatternComponentResult; + hash: URLPatternURLPatternComponentResult; +} +interface URLPatternURLPatternOptions { + ignoreCase?: boolean; +} +/** + * A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent) + */ +declare class CloseEvent extends Event { + constructor(type: string, initializer?: CloseEventInit); + /** + * Returns the WebSocket connection close code provided by the server. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code) + */ + readonly code: number; + /** + * Returns the WebSocket connection close reason provided by the server. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason) + */ + readonly reason: string; + /** + * Returns true if the connection closed cleanly; false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean) + */ + readonly wasClean: boolean; +} +interface CloseEventInit { + code?: number; + reason?: string; + wasClean?: boolean; +} +/** + * A message received by a target object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) + */ +declare class MessageEvent extends Event { + constructor(type: string, initializer: MessageEventInit); + /** + * Returns the data of the message. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) + */ + readonly data: ArrayBuffer | string; +} +interface MessageEventInit { + data: ArrayBuffer | string; +} +type WebSocketEventMap = { + close: CloseEvent; + message: MessageEvent; + open: Event; + error: ErrorEvent; +}; +/** + * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) + */ +declare var WebSocket: { + prototype: WebSocket; + new (url: string, protocols?: (string[] | string)): WebSocket; + readonly READY_STATE_CONNECTING: number; + readonly CONNECTING: number; + readonly READY_STATE_OPEN: number; + readonly OPEN: number; + readonly READY_STATE_CLOSING: number; + readonly CLOSING: number; + readonly READY_STATE_CLOSED: number; + readonly CLOSED: number; +}; +/** + * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) + */ +interface WebSocket extends EventTarget { + accept(): void; + /** + * Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send) + */ + send(message: (ArrayBuffer | ArrayBufferView) | string): void; + /** + * Closes the WebSocket connection, optionally using code as the the WebSocket connection close code and reason as the the WebSocket connection close reason. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close) + */ + close(code?: number, reason?: string): void; + serializeAttachment(attachment: any): void; + deserializeAttachment(): any | null; + /** + * Returns the state of the WebSocket object's connection. It can have the values described below. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) + */ + readyState: number; + /** + * Returns the URL that was used to establish the WebSocket connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url) + */ + url: string | null; + /** + * Returns the subprotocol selected by the server, if any. It can be used in conjunction with the array form of the constructor's second argument to perform subprotocol negotiation. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol) + */ + protocol: string | null; + /** + * Returns the extensions selected by the server, if any. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) + */ + extensions: string | null; +} +declare const WebSocketPair: { + new (): { + 0: WebSocket; + 1: WebSocket; + }; +}; +interface SqlStorage { + exec>(query: string, ...bindings: any[]): SqlStorageCursor; + get databaseSize(): number; + Cursor: typeof SqlStorageCursor; + Statement: typeof SqlStorageStatement; +} +declare abstract class SqlStorageStatement { +} +type SqlStorageValue = ArrayBuffer | string | number | null; +declare abstract class SqlStorageCursor> { + next(): { + done?: false; + value: T; + } | { + done: true; + value?: never; + }; + toArray(): T[]; + one(): T; + raw(): IterableIterator; + columnNames: string[]; + get rowsRead(): number; + get rowsWritten(): number; + [Symbol.iterator](): IterableIterator; +} +interface Socket { + get readable(): ReadableStream; + get writable(): WritableStream; + get closed(): Promise; + get opened(): Promise; + close(): Promise; + startTls(options?: TlsOptions): Socket; +} +interface SocketOptions { + secureTransport?: string; + allowHalfOpen: boolean; + highWaterMark?: (number | bigint); +} +interface SocketAddress { + hostname: string; + port: number; +} +interface TlsOptions { + expectedServerHostname?: string; +} +interface SocketInfo { + remoteAddress?: string; + localAddress?: string; +} +interface GPU { + requestAdapter(param1?: GPURequestAdapterOptions): Promise; +} +declare abstract class GPUAdapter { + requestDevice(param1?: GPUDeviceDescriptor): Promise; + requestAdapterInfo(unmaskHints?: string[]): Promise; + get features(): GPUSupportedFeatures; + get limits(): GPUSupportedLimits; +} +interface GPUDevice extends EventTarget { + createBuffer(param1: GPUBufferDescriptor): GPUBuffer; + createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout; + createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup; + createSampler(descriptor: GPUSamplerDescriptor): GPUSampler; + createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule; + createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout; + createComputePipeline(descriptor: GPUComputePipelineDescriptor): GPUComputePipeline; + createRenderPipeline(descriptor: GPURenderPipelineDescriptor): GPURenderPipeline; + createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): GPUCommandEncoder; + createTexture(param1: GPUTextureDescriptor): GPUTexture; + destroy(): void; + createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet; + pushErrorScope(filter: string): void; + popErrorScope(): Promise; + get queue(): GPUQueue; + get lost(): Promise; + get features(): GPUSupportedFeatures; + get limits(): GPUSupportedLimits; +} +interface GPUDeviceDescriptor { + label?: string; + requiredFeatures?: string[]; + requiredLimits?: Record; + defaultQueue?: GPUQueueDescriptor; +} +interface GPUBufferDescriptor { + label: string; + size: number | bigint; + usage: number; + mappedAtCreation: boolean; +} +interface GPUQueueDescriptor { + label?: string; +} +declare abstract class GPUBufferUsage { + static readonly MAP_READ: number; + static readonly MAP_WRITE: number; + static readonly COPY_SRC: number; + static readonly COPY_DST: number; + static readonly INDEX: number; + static readonly VERTEX: number; + static readonly UNIFORM: number; + static readonly STORAGE: number; + static readonly INDIRECT: number; + static readonly QUERY_RESOLVE: number; +} +interface GPUBuffer { + getMappedRange(size?: (number | bigint), param2?: (number | bigint)): ArrayBuffer; + unmap(): void; + destroy(): void; + mapAsync(offset: number, size?: (number | bigint), param3?: (number | bigint)): Promise; + get size(): number | bigint; + get usage(): number; + get mapState(): string; +} +declare abstract class GPUShaderStage { + static readonly VERTEX: number; + static readonly FRAGMENT: number; + static readonly COMPUTE: number; +} +interface GPUBindGroupLayoutDescriptor { + label?: string; + entries: GPUBindGroupLayoutEntry[]; +} +interface GPUBindGroupLayoutEntry { + binding: number; + visibility: number; + buffer?: GPUBufferBindingLayout; + sampler?: GPUSamplerBindingLayout; + texture?: GPUTextureBindingLayout; + storageTexture?: GPUStorageTextureBindingLayout; +} +interface GPUStorageTextureBindingLayout { + access?: string; + format: string; + viewDimension?: string; +} +interface GPUTextureBindingLayout { + sampleType?: string; + viewDimension?: string; + multisampled?: boolean; +} +interface GPUSamplerBindingLayout { + type?: string; +} +interface GPUBufferBindingLayout { + type?: string; + hasDynamicOffset?: boolean; + minBindingSize?: (number | bigint); +} +interface GPUBindGroupLayout { +} +interface GPUBindGroup { +} +interface GPUBindGroupDescriptor { + label?: string; + layout: GPUBindGroupLayout; + entries: GPUBindGroupEntry[]; +} +interface GPUBindGroupEntry { + binding: number; + resource: GPUBufferBinding | GPUSampler; +} +interface GPUBufferBinding { + buffer: GPUBuffer; + offset?: (number | bigint); + size?: (number | bigint); +} +interface GPUSampler { +} +interface GPUSamplerDescriptor { + label?: string; + addressModeU?: string; + addressModeV?: string; + addressModeW?: string; + magFilter?: string; + minFilter?: string; + mipmapFilter?: string; + lodMinClamp?: number; + lodMaxClamp?: number; + compare: string; + maxAnisotropy?: number; +} +interface GPUShaderModule { + getCompilationInfo(): Promise; +} +interface GPUShaderModuleDescriptor { + label?: string; + code: string; +} +interface GPUPipelineLayout { +} +interface GPUPipelineLayoutDescriptor { + label?: string; + bindGroupLayouts: GPUBindGroupLayout[]; +} +interface GPUComputePipeline { + getBindGroupLayout(index: number): GPUBindGroupLayout; +} +interface GPUComputePipelineDescriptor { + label?: string; + compute: GPUProgrammableStage; + layout: string | GPUPipelineLayout; +} +interface GPUProgrammableStage { + module: GPUShaderModule; + entryPoint: string; + constants?: Record; +} +interface GPUCommandEncoder { + get label(): string; + beginComputePass(descriptor?: GPUComputePassDescriptor): GPUComputePassEncoder; + beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder; + copyBufferToBuffer(source: GPUBuffer, sourceOffset: number | bigint, destination: GPUBuffer, destinationOffset: number | bigint, size: number | bigint): void; + finish(param0?: GPUCommandBufferDescriptor): GPUCommandBuffer; + copyTextureToBuffer(source: GPUImageCopyTexture, destination: GPUImageCopyBuffer, copySize: Iterable | GPUExtent3DDict): void; + copyBufferToTexture(source: GPUImageCopyBuffer, destination: GPUImageCopyTexture, copySize: Iterable | GPUExtent3DDict): void; + copyTextureToTexture(source: GPUImageCopyTexture, destination: GPUImageCopyTexture, copySize: Iterable | GPUExtent3DDict): void; + clearBuffer(buffer: GPUBuffer, offset?: (number | bigint), size?: (number | bigint)): void; +} +interface GPUCommandEncoderDescriptor { + label?: string; +} +interface GPUComputePassEncoder { + setPipeline(pipeline: GPUComputePipeline): void; + setBindGroup(index: number, bindGroup: GPUBindGroup | null, dynamicOffsets?: Iterable): void; + dispatchWorkgroups(workgroupCountX: number, workgroupCountY?: number, workgroupCountZ?: number): void; + end(): void; +} +interface GPUComputePassDescriptor { + label?: string; + timestampWrites?: GPUComputePassTimestampWrites; +} +interface GPUQuerySet { +} +interface GPUQuerySetDescriptor { + label?: string; +} +interface GPUComputePassTimestampWrites { + querySet: GPUQuerySet; + beginningOfPassWriteIndex?: number; + endOfPassWriteIndex?: number; +} +interface GPUCommandBufferDescriptor { + label?: string; +} +interface GPUCommandBuffer { +} +interface GPUQueue { + submit(commandBuffers: GPUCommandBuffer[]): void; + writeBuffer(buffer: GPUBuffer, bufferOffset: number | bigint, data: ArrayBuffer | ArrayBufferView, dataOffset?: (number | bigint), size?: (number | bigint)): void; +} +declare abstract class GPUMapMode { + static readonly READ: number; + static readonly WRITE: number; +} +interface GPURequestAdapterOptions { + powerPreference: string; + forceFallbackAdapter?: boolean; +} +interface GPUAdapterInfo { + get vendor(): string; + get architecture(): string; + get device(): string; + get description(): string; +} +interface GPUSupportedFeatures { + has(name: string): boolean; + keys(): string[]; +} +interface GPUSupportedLimits { + get maxTextureDimension1D(): number; + get maxTextureDimension2D(): number; + get maxTextureDimension3D(): number; + get maxTextureArrayLayers(): number; + get maxBindGroups(): number; + get maxBindingsPerBindGroup(): number; + get maxDynamicUniformBuffersPerPipelineLayout(): number; + get maxDynamicStorageBuffersPerPipelineLayout(): number; + get maxSampledTexturesPerShaderStage(): number; + get maxSamplersPerShaderStage(): number; + get maxStorageBuffersPerShaderStage(): number; + get maxStorageTexturesPerShaderStage(): number; + get maxUniformBuffersPerShaderStage(): number; + get maxUniformBufferBindingSize(): number | bigint; + get maxStorageBufferBindingSize(): number | bigint; + get minUniformBufferOffsetAlignment(): number; + get minStorageBufferOffsetAlignment(): number; + get maxVertexBuffers(): number; + get maxBufferSize(): number | bigint; + get maxVertexAttributes(): number; + get maxVertexBufferArrayStride(): number; + get maxInterStageShaderComponents(): number; + get maxInterStageShaderVariables(): number; + get maxColorAttachments(): number; + get maxColorAttachmentBytesPerSample(): number; + get maxComputeWorkgroupStorageSize(): number; + get maxComputeInvocationsPerWorkgroup(): number; + get maxComputeWorkgroupSizeX(): number; + get maxComputeWorkgroupSizeY(): number; + get maxComputeWorkgroupSizeZ(): number; + get maxComputeWorkgroupsPerDimension(): number; +} +declare abstract class GPUError { + get message(): string; +} +declare abstract class GPUOutOfMemoryError extends GPUError { +} +declare abstract class GPUInternalError extends GPUError { +} +declare abstract class GPUValidationError extends GPUError { +} +declare abstract class GPUDeviceLostInfo { + get message(): string; + get reason(): string; +} +interface GPUCompilationMessage { + get message(): string; + get type(): string; + get lineNum(): number; + get linePos(): number; + get offset(): number; + get length(): number; +} +interface GPUCompilationInfo { + get messages(): GPUCompilationMessage[]; +} +declare abstract class GPUTextureUsage { + static readonly COPY_SRC: number; + static readonly COPY_DST: number; + static readonly TEXTURE_BINDING: number; + static readonly STORAGE_BINDING: number; + static readonly RENDER_ATTACHMENT: number; +} +interface GPUTextureDescriptor { + label: string; + size: number[] | GPUExtent3DDict; + mipLevelCount?: number; + sampleCount?: number; + dimension?: string; + format: string; + usage: number; + viewFormats?: string[]; +} +interface GPUExtent3DDict { + width: number; + height?: number; + depthOrArrayLayers?: number; +} +interface GPUTexture { + createView(descriptor?: GPUTextureViewDescriptor): GPUTextureView; + destroy(): void; + get width(): number; + get height(): number; + get depthOrArrayLayers(): number; + get mipLevelCount(): number; + get dimension(): string; + get format(): string; + get usage(): number; +} +interface GPUTextureView { +} +interface GPUTextureViewDescriptor { + label: string; + format: string; + dimension: string; + aspect?: string; + baseMipLevel?: number; + mipLevelCount: number; + baseArrayLayer?: number; + arrayLayerCount: number; +} +declare abstract class GPUColorWrite { + static readonly RED: number; + static readonly GREEN: number; + static readonly BLUE: number; + static readonly ALPHA: number; + static readonly ALL: number; +} +interface GPURenderPipeline { +} +interface GPURenderPipelineDescriptor { + label?: string; + layout: string | GPUPipelineLayout; + vertex: GPUVertexState; + primitive?: GPUPrimitiveState; + depthStencil?: GPUDepthStencilState; + multisample?: GPUMultisampleState; + fragment?: GPUFragmentState; +} +interface GPUVertexState { + module: GPUShaderModule; + entryPoint: string; + constants?: Record; + buffers?: GPUVertexBufferLayout[]; +} +interface GPUVertexBufferLayout { + arrayStride: number | bigint; + stepMode?: string; + attributes: GPUVertexAttribute[]; +} +interface GPUVertexAttribute { + format: string; + offset: number | bigint; + shaderLocation: number; +} +interface GPUPrimitiveState { + topology?: string; + stripIndexFormat?: string; + frontFace?: string; + cullMode?: string; + unclippedDepth?: boolean; +} +interface GPUStencilFaceState { + compare?: string; + failOp?: string; + depthFailOp?: string; + passOp?: string; +} +interface GPUDepthStencilState { + format: string; + depthWriteEnabled: boolean; + depthCompare: string; + stencilFront?: GPUStencilFaceState; + stencilBack?: GPUStencilFaceState; + stencilReadMask?: number; + stencilWriteMask?: number; + depthBias?: number; + depthBiasSlopeScale?: number; + depthBiasClamp?: number; +} +interface GPUMultisampleState { + count?: number; + mask?: number; + alphaToCoverageEnabled?: boolean; +} +interface GPUFragmentState { + module: GPUShaderModule; + entryPoint: string; + constants?: Record; + targets: GPUColorTargetState[]; +} +interface GPUColorTargetState { + format: string; + blend: GPUBlendState; + writeMask?: number; +} +interface GPUBlendState { + color: GPUBlendComponent; + alpha: GPUBlendComponent; +} +interface GPUBlendComponent { + operation?: string; + srcFactor?: string; + dstFactor?: string; +} +interface GPURenderPassEncoder { + setPipeline(pipeline: GPURenderPipeline): void; + draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void; + end(): void; +} +interface GPURenderPassDescriptor { + label?: string; + colorAttachments: GPURenderPassColorAttachment[]; + depthStencilAttachment?: GPURenderPassDepthStencilAttachment; + occlusionQuerySet?: GPUQuerySet; + timestampWrites?: GPURenderPassTimestampWrites; + maxDrawCount?: (number | bigint); +} +interface GPURenderPassColorAttachment { + view: GPUTextureView; + depthSlice?: number; + resolveTarget?: GPUTextureView; + clearValue?: (number[] | GPUColorDict); + loadOp: string; + storeOp: string; +} +interface GPUColorDict { + r: number; + g: number; + b: number; + a: number; +} +interface GPURenderPassDepthStencilAttachment { + view: GPUTextureView; + depthClearValue?: number; + depthLoadOp?: string; + depthStoreOp?: string; + depthReadOnly?: boolean; + stencilClearValue?: number; + stencilLoadOp?: string; + stencilStoreOp?: string; + stencilReadOnly?: boolean; +} +interface GPURenderPassTimestampWrites { + querySet: GPUQuerySet; + beginningOfPassWriteIndex?: number; + endOfPassWriteIndex?: number; +} +interface GPUImageCopyTexture { + texture: GPUTexture; + mipLevel?: number; + origin?: (number[] | GPUOrigin3DDict); + aspect?: string; +} +interface GPUImageCopyBuffer { + buffer: GPUBuffer; + offset?: (number | bigint); + bytesPerRow?: number; + rowsPerImage?: number; +} +interface GPUOrigin3DDict { + x?: number; + y?: number; + z?: number; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) */ +declare class EventSource extends EventTarget { + constructor(url: string, init?: EventSourceEventSourceInit); + /** + * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) + */ + close(): void; + /** + * Returns the URL providing the event stream. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) + */ + get url(): string; + /** + * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) + */ + get withCredentials(): boolean; + /** + * Returns the state of this EventSource object's connection. It can have the values described below. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) + */ + get readyState(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ + get onopen(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ + set onopen(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ + get onmessage(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ + set onmessage(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ + get onerror(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ + set onerror(value: any | null); + static readonly CONNECTING: number; + static readonly OPEN: number; + static readonly CLOSED: number; + static from(stream: ReadableStream): EventSource; +} +interface EventSourceEventSourceInit { + withCredentials?: boolean; + fetcher?: Fetcher; +} +interface Container { + get running(): boolean; + start(options?: ContainerStartupOptions): void; + monitor(): Promise; + destroy(error?: any): Promise; + signal(signo: number): void; + getTcpPort(port: number): Fetcher; +} +interface ContainerStartupOptions { + entrypoint?: string[]; + enableInternet: boolean; + env?: Record; +} +type AiImageClassificationInput = { + image: number[]; +}; +type AiImageClassificationOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiImageClassification { + inputs: AiImageClassificationInput; + postProcessedOutputs: AiImageClassificationOutput; +} +type AiImageToTextInput = { + image: number[]; + prompt?: string; + max_tokens?: number; + temperature?: number; + top_p?: number; + top_k?: number; + seed?: number; + repetition_penalty?: number; + frequency_penalty?: number; + presence_penalty?: number; + raw?: boolean; + messages?: RoleScopedChatInput[]; +}; +type AiImageToTextOutput = { + description: string; +}; +declare abstract class BaseAiImageToText { + inputs: AiImageToTextInput; + postProcessedOutputs: AiImageToTextOutput; +} +type AiObjectDetectionInput = { + image: number[]; +}; +type AiObjectDetectionOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiObjectDetection { + inputs: AiObjectDetectionInput; + postProcessedOutputs: AiObjectDetectionOutput; +} +type AiSentenceSimilarityInput = { + source: string; + sentences: string[]; +}; +type AiSentenceSimilarityOutput = number[]; +declare abstract class BaseAiSentenceSimilarity { + inputs: AiSentenceSimilarityInput; + postProcessedOutputs: AiSentenceSimilarityOutput; +} +type AiAutomaticSpeechRecognitionInput = { + audio: number[]; +}; +type AiAutomaticSpeechRecognitionOutput = { + text?: string; + words?: { + word: string; + start: number; + end: number; + }[]; + vtt?: string; +}; +declare abstract class BaseAiAutomaticSpeechRecognition { + inputs: AiAutomaticSpeechRecognitionInput; + postProcessedOutputs: AiAutomaticSpeechRecognitionOutput; +} +type AiSummarizationInput = { + input_text: string; + max_length?: number; +}; +type AiSummarizationOutput = { + summary: string; +}; +declare abstract class BaseAiSummarization { + inputs: AiSummarizationInput; + postProcessedOutputs: AiSummarizationOutput; +} +type AiTextClassificationInput = { + text: string; +}; +type AiTextClassificationOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiTextClassification { + inputs: AiTextClassificationInput; + postProcessedOutputs: AiTextClassificationOutput; +} +type AiTextEmbeddingsInput = { + text: string | string[]; +}; +type AiTextEmbeddingsOutput = { + shape: number[]; + data: number[][]; +}; +declare abstract class BaseAiTextEmbeddings { + inputs: AiTextEmbeddingsInput; + postProcessedOutputs: AiTextEmbeddingsOutput; +} +type RoleScopedChatInput = { + role: "user" | "assistant" | "system" | "tool" | (string & NonNullable); + content: string; + name?: string; +}; +type AiTextGenerationToolLegacyInput = { + name: string; + description: string; + parameters?: { + type: "object" | (string & NonNullable); + properties: { + [key: string]: { + type: string; + description?: string; + }; + }; + required: string[]; + }; +}; +type AiTextGenerationToolInput = { + type: "function" | (string & NonNullable); + function: { + name: string; + description: string; + parameters?: { + type: "object" | (string & NonNullable); + properties: { + [key: string]: { + type: string; + description?: string; + }; + }; + required: string[]; + }; + }; +}; +type AiTextGenerationFunctionsInput = { + name: string; + code: string; +}; +type AiTextGenerationResponseFormat = { + type: string; + json_schema?: any; +}; +type AiTextGenerationInput = { + prompt?: string; + raw?: boolean; + stream?: boolean; + max_tokens?: number; + temperature?: number; + top_p?: number; + top_k?: number; + seed?: number; + repetition_penalty?: number; + frequency_penalty?: number; + presence_penalty?: number; + messages?: RoleScopedChatInput[]; + response_format?: AiTextGenerationResponseFormat; + tools?: AiTextGenerationToolInput[] | AiTextGenerationToolLegacyInput[] | (object & NonNullable); + functions?: AiTextGenerationFunctionsInput[]; +}; +type AiTextGenerationOutput = { + response?: string; + tool_calls?: { + name: string; + arguments: unknown; + }[]; +} | ReadableStream; +declare abstract class BaseAiTextGeneration { + inputs: AiTextGenerationInput; + postProcessedOutputs: AiTextGenerationOutput; +} +type AiTextToSpeechInput = { + prompt: string; + lang?: string; +}; +type AiTextToSpeechOutput = Uint8Array | { + audio: string; +}; +declare abstract class BaseAiTextToSpeech { + inputs: AiTextToSpeechInput; + postProcessedOutputs: AiTextToSpeechOutput; +} +type AiTextToImageInput = { + prompt: string; + negative_prompt?: string; + height?: number; + width?: number; + image?: number[]; + image_b64?: string; + mask?: number[]; + num_steps?: number; + strength?: number; + guidance?: number; + seed?: number; +}; +type AiTextToImageOutput = ReadableStream; +declare abstract class BaseAiTextToImage { + inputs: AiTextToImageInput; + postProcessedOutputs: AiTextToImageOutput; +} +type AiTranslationInput = { + text: string; + target_lang: string; + source_lang?: string; +}; +type AiTranslationOutput = { + translated_text?: string; +}; +declare abstract class BaseAiTranslation { + inputs: AiTranslationInput; + postProcessedOutputs: AiTranslationOutput; +} +type Ai_Cf_Openai_Whisper_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; +interface Ai_Cf_Openai_Whisper_Output { + /** + * The transcription + */ + text: string; + word_count?: number; + words?: { + word?: string; + /** + * The second this word begins in the recording + */ + start?: number; + /** + * The ending second when the word completes + */ + end?: number; + }[]; + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper { + inputs: Ai_Cf_Openai_Whisper_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; +} +type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { + /** + * The input text prompt for the model to generate a response. + */ + prompt?: string; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + image: number[] | (string & NonNullable); + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; +}; +interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { + description?: string; +} +declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { + inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; + postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; +} +type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; +interface Ai_Cf_Openai_Whisper_Tiny_En_Output { + /** + * The transcription + */ + text: string; + word_count?: number; + words?: { + word?: string; + /** + * The second this word begins in the recording + */ + start?: number; + /** + * The ending second when the word completes + */ + end?: number; + }[]; + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { + inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; +} +interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { + /** + * Base64 encoded value of the audio data. + */ + audio: string; + /** + * Supported tasks are 'translate' or 'transcribe'. + */ + task?: string; + /** + * The language of the audio being transcribed or translated. + */ + language?: string; + /** + * Preprocess the audio with a voice activity detection model. + */ + vad_filter?: string; + /** + * A text prompt to help provide context to the model on the contents of the audio. + */ + initial_prompt?: string; + /** + * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. + */ + prefix?: string; +} +interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { + transcription_info?: { + /** + * The language of the audio being transcribed or translated. + */ + language?: string; + /** + * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1. + */ + language_probability?: number; + /** + * The total duration of the original audio file, in seconds. + */ + duration?: number; + /** + * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds. + */ + duration_after_vad?: number; + }; + /** + * The complete transcription of the audio. + */ + text: string; + /** + * The total number of words in the transcription. + */ + word_count?: number; + segments?: { + /** + * The starting time of the segment within the audio, in seconds. + */ + start?: number; + /** + * The ending time of the segment within the audio, in seconds. + */ + end?: number; + /** + * The transcription of the segment. + */ + text?: string; + /** + * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs. + */ + temperature?: number; + /** + * The average log probability of the predictions for the words in this segment, indicating overall confidence. + */ + avg_logprob?: number; + /** + * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process. + */ + compression_ratio?: number; + /** + * The probability that the segment contains no speech, represented as a decimal between 0 and 1. + */ + no_speech_prob?: number; + words?: { + /** + * The individual word transcribed from the audio. + */ + word?: string; + /** + * The starting time of the word within the audio, in seconds. + */ + start?: number; + /** + * The ending time of the word within the audio, in seconds. + */ + end?: number; + }[]; + }[]; + /** + * The transcription in WebVTT format, which includes timing and text information for use in subtitles. + */ + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { + inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { + /** + * A text description of the image you want to generate. + */ + prompt: string; + /** + * The number of diffusion steps; higher values can improve quality but take longer. + */ + steps?: number; +} +interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { + /** + * The generated image in Base64 format. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { + inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages; +interface Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + image?: number[] | (string & NonNullable); + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; +} +interface Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role: string; + /** + * The content of the message as a string. + */ + content: string; + }[]; + image?: number[] | string; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * If true, the response will be streamed back incrementally. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { + /** + * The generated text response from the model + */ + response?: string; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +} | ReadableStream; +declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { + inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender must alternate between 'user' and 'assistant'. + */ + role: "user" | "assistant"; + /** + * The content of the message as a string. + */ + content: string; + }[]; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Dictate the output format of the generated response. + */ + response_format?: { + /** + * Set to json_object to process and output generated text as JSON. + */ + type?: string; + }; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { + response?: string | { + /** + * Whether the conversation is safe or not. + */ + safe?: boolean; + /** + * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. + */ + categories?: string[]; + }; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; +} +declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { + inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; +} +interface AiModels { + "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; + "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; + "@cf/runwayml/stable-diffusion-v1-5-inpainting": BaseAiTextToImage; + "@cf/runwayml/stable-diffusion-v1-5-img2img": BaseAiTextToImage; + "@cf/lykon/dreamshaper-8-lcm": BaseAiTextToImage; + "@cf/bytedance/stable-diffusion-xl-lightning": BaseAiTextToImage; + "@cf/baai/bge-base-en-v1.5": BaseAiTextEmbeddings; + "@cf/baai/bge-small-en-v1.5": BaseAiTextEmbeddings; + "@cf/baai/bge-large-en-v1.5": BaseAiTextEmbeddings; + "@cf/microsoft/resnet-50": BaseAiImageClassification; + "@cf/facebook/detr-resnet-50": BaseAiObjectDetection; + "@cf/meta/llama-2-7b-chat-int8": BaseAiTextGeneration; + "@cf/mistral/mistral-7b-instruct-v0.1": BaseAiTextGeneration; + "@cf/meta/llama-2-7b-chat-fp16": BaseAiTextGeneration; + "@hf/thebloke/llama-2-13b-chat-awq": BaseAiTextGeneration; + "@hf/thebloke/mistral-7b-instruct-v0.1-awq": BaseAiTextGeneration; + "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; + "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; + "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; + "@hf/thebloke/llamaguard-7b-awq": BaseAiTextGeneration; + "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; + "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; + "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; + "@cf/defog/sqlcoder-7b-2": BaseAiTextGeneration; + "@cf/openchat/openchat-3.5-0106": BaseAiTextGeneration; + "@cf/tiiuae/falcon-7b-instruct": BaseAiTextGeneration; + "@cf/thebloke/discolm-german-7b-v1-awq": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-0.5b-chat": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-7b-chat-awq": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-14b-chat-awq": BaseAiTextGeneration; + "@cf/tinyllama/tinyllama-1.1b-chat-v1.0": BaseAiTextGeneration; + "@cf/microsoft/phi-2": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-1.8b-chat": BaseAiTextGeneration; + "@cf/mistral/mistral-7b-instruct-v0.2-lora": BaseAiTextGeneration; + "@hf/nousresearch/hermes-2-pro-mistral-7b": BaseAiTextGeneration; + "@hf/nexusflow/starling-lm-7b-beta": BaseAiTextGeneration; + "@hf/google/gemma-7b-it": BaseAiTextGeneration; + "@cf/meta-llama/llama-2-7b-chat-hf-lora": BaseAiTextGeneration; + "@cf/google/gemma-2b-it-lora": BaseAiTextGeneration; + "@cf/google/gemma-7b-it-lora": BaseAiTextGeneration; + "@hf/mistral/mistral-7b-instruct-v0.2": BaseAiTextGeneration; + "@cf/meta/llama-3-8b-instruct": BaseAiTextGeneration; + "@cf/fblgit/una-cybertron-7b-v2-bf16": BaseAiTextGeneration; + "@cf/meta/llama-3-8b-instruct-awq": BaseAiTextGeneration; + "@hf/meta-llama/meta-llama-3-8b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct-fp8": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct-awq": BaseAiTextGeneration; + "@cf/meta/llama-3.2-3b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.2-1b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.3-70b-instruct-fp8-fast": BaseAiTextGeneration; + "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": BaseAiTextGeneration; + "@cf/meta/m2m100-1.2b": BaseAiTranslation; + "@cf/facebook/bart-large-cnn": BaseAiSummarization; + "@cf/llava-hf/llava-1.5-7b-hf": BaseAiImageToText; + "@cf/openai/whisper": Base_Ai_Cf_Openai_Whisper; + "@cf/unum/uform-gen2-qwen-500m": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; + "@cf/openai/whisper-tiny-en": Base_Ai_Cf_Openai_Whisper_Tiny_En; + "@cf/openai/whisper-large-v3-turbo": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; + "@cf/black-forest-labs/flux-1-schnell": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; + "@cf/meta/llama-3.2-11b-vision-instruct": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; + "@cf/meta/llama-guard-3-8b": Base_Ai_Cf_Meta_Llama_Guard_3_8B; +} +type AiOptions = { + gateway?: GatewayOptions; + returnRawResponse?: boolean; + prefix?: string; + extraHeaders?: object; +}; +type ConversionResponse = { + name: string; + mimeType: string; + format: "markdown"; + tokens: number; + data: string; +}; +type AiModelsSearchParams = { + author?: string; + hide_experimental?: boolean; + page?: number; + per_page?: number; + search?: string; + source?: number; + task?: string; +}; +type AiModelsSearchObject = { + id: string; + source: number; + name: string; + description: string; + task: { + id: string; + name: string; + description: string; + }; + tags: string[]; + properties: { + property_id: string; + value: string; + }[]; +}; +interface InferenceUpstreamError extends Error { +} +interface AiInternalError extends Error { +} +type AiModelListType = Record; +declare abstract class Ai { + aiGatewayLogId: string | null; + gateway(gatewayId: string): AiGateway; + autorag(autoragId: string): AutoRAG; + run(model: Name, inputs: AiModelList[Name]["inputs"], options?: Options): Promise; + public models(params?: AiModelsSearchParams): Promise; + public toMarkdown(files: { + name: string; + blob: Blob; + }[], options?: { + gateway?: GatewayOptions; + extraHeaders?: object; + }): Promise; + public toMarkdown(files: { + name: string; + blob: Blob; + }, options?: { + gateway?: GatewayOptions; + extraHeaders?: object; + }): Promise; +} +type GatewayOptions = { + id: string; + cacheKey?: string; + cacheTtl?: number; + skipCache?: boolean; + metadata?: Record; + collectLog?: boolean; +}; +type AiGatewayPatchLog = { + score?: number | null; + feedback?: -1 | 1 | null; + metadata?: Record | null; +}; +type AiGatewayLog = { + id: string; + provider: string; + model: string; + model_type?: string; + path: string; + duration: number; + request_type?: string; + request_content_type?: string; + status_code: number; + response_content_type?: string; + success: boolean; + cached: boolean; + tokens_in?: number; + tokens_out?: number; + metadata?: Record; + step?: number; + cost?: number; + custom_cost?: boolean; + request_size: number; + request_head?: string; + request_head_complete: boolean; + response_size: number; + response_head?: string; + response_head_complete: boolean; + created_at: Date; +}; +type AIGatewayProviders = "workers-ai" | "anthropic" | "aws-bedrock" | "azure-openai" | "google-vertex-ai" | "huggingface" | "openai" | "perplexity-ai" | "replicate" | "groq" | "cohere" | "google-ai-studio" | "mistral" | "grok" | "openrouter" | "deepseek" | "cerebras" | "cartesia" | "elevenlabs" | "adobe-firefly"; +type AIGatewayHeaders = { + "cf-aig-metadata": Record | string; + "cf-aig-custom-cost": { + per_token_in?: number; + per_token_out?: number; + } | { + total_cost?: number; + } | string; + "cf-aig-cache-ttl": number | string; + "cf-aig-skip-cache": boolean | string; + "cf-aig-cache-key": string; + "cf-aig-collect-log": boolean | string; + Authorization: string; + "Content-Type": string; + [key: string]: string | number | boolean | object; +}; +type AIGatewayUniversalRequest = { + provider: AIGatewayProviders | string; + endpoint: string; + headers: Partial; + query: unknown; +}; +interface AiGatewayInternalError extends Error { +} +interface AiGatewayLogNotFound extends Error { +} +declare abstract class AiGateway { + patchLog(logId: string, data: AiGatewayPatchLog): Promise; + getLog(logId: string): Promise; + run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[]): Promise; + getUrl(provider?: AIGatewayProviders | string): Promise; +} +interface AutoRAGInternalError extends Error { +} +interface AutoRAGNotFoundError extends Error { +} +interface AutoRAGUnauthorizedError extends Error { +} +type AutoRagSearchRequest = { + query: string; + max_num_results?: number; + ranking_options?: { + ranker?: string; + score_threshold?: number; + }; + rewrite_query?: boolean; +}; +type AutoRagSearchResponse = { + object: "vector_store.search_results.page"; + search_query: string; + data: { + file_id: string; + filename: string; + score: number; + attributes: Record; + content: { + type: "text"; + text: string; + }[]; + }[]; + has_more: boolean; + next_page: string | null; +}; +type AutoRagAiSearchResponse = AutoRagSearchResponse & { + response: string; +}; +declare abstract class AutoRAG { + search(params: AutoRagSearchRequest): Promise; + aiSearch(params: AutoRagSearchRequest): Promise; +} +interface BasicImageTransformations { + /** + * Maximum width in image pixels. The value must be an integer. + */ + width?: number; + /** + * Maximum height in image pixels. The value must be an integer. + */ + height?: number; + /** + * Resizing mode as a string. It affects interpretation of width and height + * options: + * - scale-down: Similar to contain, but the image is never enlarged. If + * the image is larger than given width or height, it will be resized. + * Otherwise its original size will be kept. + * - contain: Resizes to maximum size that fits within the given width and + * height. If only a single dimension is given (e.g. only width), the + * image will be shrunk or enlarged to exactly match that dimension. + * Aspect ratio is always preserved. + * - cover: Resizes (shrinks or enlarges) to fill the entire area of width + * and height. If the image has an aspect ratio different from the ratio + * of width and height, it will be cropped to fit. + * - crop: The image will be shrunk and cropped to fit within the area + * specified by width and height. The image will not be enlarged. For images + * smaller than the given dimensions it's the same as scale-down. For + * images larger than the given dimensions, it's the same as cover. + * See also trim. + * - pad: Resizes to the maximum size that fits within the given width and + * height, and then fills the remaining area with a background color + * (white by default). Use of this mode is not recommended, as the same + * effect can be more efficiently achieved with the contain mode and the + * CSS object-fit: contain property. + */ + fit?: "scale-down" | "contain" | "cover" | "crop" | "pad"; + /** + * When cropping with fit: "cover", this defines the side or point that should + * be left uncropped. The value is either a string + * "left", "right", "top", "bottom", "auto", or "center" (the default), + * or an object {x, y} containing focal point coordinates in the original + * image expressed as fractions ranging from 0.0 (top or left) to 1.0 + * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will + * crop bottom or left and right sides as necessary, but won’t crop anything + * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to + * preserve as much as possible around a point at 20% of the height of the + * source image. + */ + gravity?: "left" | "right" | "top" | "bottom" | "center" | "auto" | BasicImageTransformationsGravityCoordinates; + /** + * Background color to add underneath the image. Applies only to images with + * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…), + * hsl(…), etc.) + */ + background?: string; + /** + * Number of degrees (90, 180, 270) to rotate the image by. width and height + * options refer to axes after rotation. + */ + rotate?: 0 | 90 | 180 | 270 | 360; +} +interface BasicImageTransformationsGravityCoordinates { + x: number; + y: number; +} +/** + * In addition to the properties you can set in the RequestInit dict + * that you pass as an argument to the Request constructor, you can + * set certain properties of a `cf` object to control how Cloudflare + * features are applied to that new Request. + * + * Note: Currently, these properties cannot be tested in the + * playground. + */ +interface RequestInitCfProperties extends Record { + cacheEverything?: boolean; + /** + * A request's cache key is what determines if two requests are + * "the same" for caching purposes. If a request has the same cache key + * as some previous request, then we can serve the same cached response for + * both. (e.g. 'some-key') + * + * Only available for Enterprise customers. + */ + cacheKey?: string; + /** + * This allows you to append additional Cache-Tag response headers + * to the origin response without modifications to the origin server. + * This will allow for greater control over the Purge by Cache Tag feature + * utilizing changes only in the Workers process. + */ + cacheTags?: string[]; + /** + * Force response to be cached for a given number of seconds. (e.g. 300) + */ + cacheTtl?: number; + /** + * Force response to be cached for a given number of seconds based on the Origin status code. + * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) + */ + cacheTtlByStatus?: Record; + scrapeShield?: boolean; + apps?: boolean; + image?: RequestInitCfPropertiesImage; + minify?: RequestInitCfPropertiesImageMinify; + mirage?: boolean; + polish?: "lossy" | "lossless" | "off"; + r2?: RequestInitCfPropertiesR2; + /** + * Redirects the request to an alternate origin server. You can use this, + * for example, to implement load balancing across several origins. + * (e.g.us-east.example.com) + * + * Note - For security reasons, the hostname set in resolveOverride must + * be proxied on the same Cloudflare zone of the incoming request. + * Otherwise, the setting is ignored. CNAME hosts are allowed, so to + * resolve to a host under a different domain or a DNS only domain first + * declare a CNAME record within your own zone’s DNS mapping to the + * external hostname, set proxy on Cloudflare, then set resolveOverride + * to point to that CNAME record. + */ + resolveOverride?: string; +} +interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations { + /** + * Absolute URL of the image file to use for the drawing. It can be any of + * the supported file formats. For drawing of watermarks or non-rectangular + * overlays we recommend using PNG or WebP images. + */ + url: string; + /** + * Floating-point number between 0 (transparent) and 1 (opaque). + * For example, opacity: 0.5 makes overlay semitransparent. + */ + opacity?: number; + /** + * - If set to true, the overlay image will be tiled to cover the entire + * area. This is useful for stock-photo-like watermarks. + * - If set to "x", the overlay image will be tiled horizontally only + * (form a line). + * - If set to "y", the overlay image will be tiled vertically only + * (form a line). + */ + repeat?: true | "x" | "y"; + /** + * Position of the overlay image relative to a given edge. Each property is + * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10 + * positions left side of the overlay 10 pixels from the left edge of the + * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom + * of the background image. + * + * Setting both left & right, or both top & bottom is an error. + * + * If no position is specified, the image will be centered. + */ + top?: number; + left?: number; + bottom?: number; + right?: number; +} +interface RequestInitCfPropertiesImage extends BasicImageTransformations { + /** + * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it + * easier to specify higher-DPI sizes in . + */ + dpr?: number; + /** + * An object with four properties {left, top, right, bottom} that specify + * a number of pixels to cut off on each side. Allows removal of borders + * or cutting out a specific fragment of an image. Trimming is performed + * before resizing or rotation. Takes dpr into account. + */ + trim?: { + left?: number; + top?: number; + right?: number; + bottom?: number; + }; + /** + * Quality setting from 1-100 (useful values are in 60-90 range). Lower values + * make images look worse, but load faster. The default is 85. It applies only + * to JPEG and WebP images. It doesn’t have any effect on PNG. + */ + quality?: number; + /** + * Output format to generate. It can be: + * - avif: generate images in AVIF format. + * - webp: generate images in Google WebP format. Set quality to 100 to get + * the WebP-lossless format. + * - json: instead of generating an image, outputs information about the + * image, in JSON format. The JSON object will contain image size + * (before and after resizing), source image’s MIME type, file size, etc. + * - jpeg: generate images in JPEG format. + * - png: generate images in PNG format. + */ + format?: "avif" | "webp" | "json" | "jpeg" | "png"; + /** + * Whether to preserve animation frames from input files. Default is true. + * Setting it to false reduces animations to still images. This setting is + * recommended when enlarging images or processing arbitrary user content, + * because large GIF animations can weigh tens or even hundreds of megabytes. + * It is also useful to set anim:false when using format:"json" to get the + * response quicker without the number of frames. + */ + anim?: boolean; + /** + * What EXIF data should be preserved in the output image. Note that EXIF + * rotation and embedded color profiles are always applied ("baked in" into + * the image), and aren't affected by this option. Note that if the Polish + * feature is enabled, all metadata may have been removed already and this + * option may have no effect. + * - keep: Preserve most of EXIF metadata, including GPS location if there's + * any. + * - copyright: Only keep the copyright tag, and discard everything else. + * This is the default behavior for JPEG files. + * - none: Discard all invisible EXIF metadata. Currently WebP and PNG + * output formats always discard metadata. + */ + metadata?: "keep" | "copyright" | "none"; + /** + * Strength of sharpening filter to apply to the image. Floating-point + * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a + * recommended value for downscaled images. + */ + sharpen?: number; + /** + * Radius of a blur filter (approximate gaussian). Maximum supported radius + * is 250. + */ + blur?: number; + /** + * Overlays are drawn in the order they appear in the array (last array + * entry is the topmost layer). + */ + draw?: RequestInitCfPropertiesImageDraw[]; + /** + * Fetching image from authenticated origin. Setting this property will + * pass authentication headers (Authorization, Cookie, etc.) through to + * the origin. + */ + "origin-auth"?: "share-publicly"; + /** + * Adds a border around the image. The border is added after resizing. Border + * width takes dpr into account, and can be specified either using a single + * width property, or individually for each side. + */ + border?: { + color: string; + width: number; + } | { + color: string; + top: number; + right: number; + bottom: number; + left: number; + }; + /** + * Increase brightness by a factor. A value of 1.0 equals no change, a value + * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright. + * 0 is ignored. + */ + brightness?: number; + /** + * Increase contrast by a factor. A value of 1.0 equals no change, a value of + * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is + * ignored. + */ + contrast?: number; + /** + * Increase exposure by a factor. A value of 1.0 equals no change, a value of + * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored. + */ + gamma?: number; + /** + * Slightly reduces latency on a cache miss by selecting a + * quickest-to-compress file format, at a cost of increased file size and + * lower image quality. It will usually override the format option and choose + * JPEG over WebP or AVIF. We do not recommend using this option, except in + * unusual circumstances like resizing uncacheable dynamically-generated + * images. + */ + compression?: "fast"; +} +interface RequestInitCfPropertiesImageMinify { + javascript?: boolean; + css?: boolean; + html?: boolean; +} +interface RequestInitCfPropertiesR2 { + /** + * Colo id of bucket that an object is stored in + */ + bucketColoId?: number; +} +/** + * Request metadata provided by Cloudflare's edge. + */ +type IncomingRequestCfProperties = IncomingRequestCfPropertiesBase & IncomingRequestCfPropertiesBotManagementEnterprise & IncomingRequestCfPropertiesCloudflareForSaaSEnterprise & IncomingRequestCfPropertiesGeographicInformation & IncomingRequestCfPropertiesCloudflareAccessOrApiShield; +interface IncomingRequestCfPropertiesBase extends Record { + /** + * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request. + * + * @example 395747 + */ + asn: number; + /** + * The organization which owns the ASN of the incoming request. + * + * @example "Google Cloud" + */ + asOrganization: string; + /** + * The original value of the `Accept-Encoding` header if Cloudflare modified it. + * + * @example "gzip, deflate, br" + */ + clientAcceptEncoding?: string; + /** + * The number of milliseconds it took for the request to reach your worker. + * + * @example 22 + */ + clientTcpRtt?: number; + /** + * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code) + * airport code of the data center that the request hit. + * + * @example "DFW" + */ + colo: string; + /** + * Represents the upstream's response to a + * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html) + * from cloudflare. + * + * For workers with no upstream, this will always be `1`. + * + * @example 3 + */ + edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus; + /** + * The HTTP Protocol the request used. + * + * @example "HTTP/2" + */ + httpProtocol: string; + /** + * The browser-requested prioritization information in the request object. + * + * If no information was set, defaults to the empty string `""` + * + * @example "weight=192;exclusive=0;group=3;group-weight=127" + * @default "" + */ + requestPriority: string; + /** + * The TLS version of the connection to Cloudflare. + * In requests served over plaintext (without TLS), this property is the empty string `""`. + * + * @example "TLSv1.3" + */ + tlsVersion: string; + /** + * The cipher for the connection to Cloudflare. + * In requests served over plaintext (without TLS), this property is the empty string `""`. + * + * @example "AEAD-AES128-GCM-SHA256" + */ + tlsCipher: string; + /** + * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake. + * + * If the incoming request was served over plaintext (without TLS) this field is undefined. + */ + tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata; +} +interface IncomingRequestCfPropertiesBotManagementBase { + /** + * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot, + * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human). + * + * @example 54 + */ + score: number; + /** + * A boolean value that is true if the request comes from a good bot, like Google or Bing. + * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots). + */ + verifiedBot: boolean; + /** + * A boolean value that is true if the request originates from a + * Cloudflare-verified proxy service. + */ + corporateProxy: boolean; + /** + * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources. + */ + staticResource: boolean; + /** + * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request). + */ + detectionIds: number[]; +} +interface IncomingRequestCfPropertiesBotManagement { + /** + * Results of Cloudflare's Bot Management analysis + */ + botManagement: IncomingRequestCfPropertiesBotManagementBase; + /** + * Duplicate of `botManagement.score`. + * + * @deprecated + */ + clientTrustScore: number; +} +interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement { + /** + * Results of Cloudflare's Bot Management analysis + */ + botManagement: IncomingRequestCfPropertiesBotManagementBase & { + /** + * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients + * across different destination IPs, Ports, and X509 certificates. + */ + ja3Hash: string; + }; +} +interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise { + /** + * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/). + * + * This field is only present if you have Cloudflare for SaaS enabled on your account + * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)). + */ + hostMetadata: HostMetadata; +} +interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield { + /** + * Information about the client certificate presented to Cloudflare. + * + * This is populated when the incoming request is served over TLS using + * either Cloudflare Access or API Shield (mTLS) + * and the presented SSL certificate has a valid + * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number) + * (i.e., not `null` or `""`). + * + * Otherwise, a set of placeholder values are used. + * + * The property `certPresented` will be set to `"1"` when + * the object is populated (i.e. the above conditions were met). + */ + tlsClientAuth: IncomingRequestCfPropertiesTLSClientAuth | IncomingRequestCfPropertiesTLSClientAuthPlaceholder; +} +/** + * Metadata about the request's TLS handshake + */ +interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata { + /** + * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal + * + * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" + */ + clientHandshake: string; + /** + * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal + * + * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" + */ + serverHandshake: string; + /** + * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal + * + * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" + */ + clientFinished: string; + /** + * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal + * + * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" + */ + serverFinished: string; +} +/** + * Geographic data about the request's origin. + */ +interface IncomingRequestCfPropertiesGeographicInformation { + /** + * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from. + * + * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `"T1"`, indicating a request that originated over TOR. + * + * If Cloudflare is unable to determine where the request originated this property is omitted. + * + * The country code `"T1"` is used for requests originating on TOR. + * + * @example "GB" + */ + country?: Iso3166Alpha2Code | "T1"; + /** + * If present, this property indicates that the request originated in the EU + * + * @example "1" + */ + isEUCountry?: "1"; + /** + * A two-letter code indicating the continent the request originated from. + * + * @example "AN" + */ + continent?: ContinentCode; + /** + * The city the request originated from + * + * @example "Austin" + */ + city?: string; + /** + * Postal code of the incoming request + * + * @example "78701" + */ + postalCode?: string; + /** + * Latitude of the incoming request + * + * @example "30.27130" + */ + latitude?: string; + /** + * Longitude of the incoming request + * + * @example "-97.74260" + */ + longitude?: string; + /** + * Timezone of the incoming request + * + * @example "America/Chicago" + */ + timezone?: string; + /** + * If known, the ISO 3166-2 name for the first level region associated with + * the IP address of the incoming request + * + * @example "Texas" + */ + region?: string; + /** + * If known, the ISO 3166-2 code for the first-level region associated with + * the IP address of the incoming request + * + * @example "TX" + */ + regionCode?: string; + /** + * Metro code (DMA) of the incoming request + * + * @example "635" + */ + metroCode?: string; +} +/** Data about the incoming request's TLS certificate */ +interface IncomingRequestCfPropertiesTLSClientAuth { + /** Always `"1"`, indicating that the certificate was presented */ + certPresented: "1"; + /** + * Result of certificate verification. + * + * @example "FAILED:self signed certificate" + */ + certVerified: Exclude; + /** The presented certificate's revokation status. + * + * - A value of `"1"` indicates the certificate has been revoked + * - A value of `"0"` indicates the certificate has not been revoked + */ + certRevoked: "1" | "0"; + /** + * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) + * + * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certIssuerDN: string; + /** + * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) + * + * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certSubjectDN: string; + /** + * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) + * + * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certIssuerDNRFC2253: string; + /** + * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) + * + * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certSubjectDNRFC2253: string; + /** The certificate issuer's distinguished name (legacy policies) */ + certIssuerDNLegacy: string; + /** The certificate subject's distinguished name (legacy policies) */ + certSubjectDNLegacy: string; + /** + * The certificate's serial number + * + * @example "00936EACBE07F201DF" + */ + certSerial: string; + /** + * The certificate issuer's serial number + * + * @example "2489002934BDFEA34" + */ + certIssuerSerial: string; + /** + * The certificate's Subject Key Identifier + * + * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" + */ + certSKI: string; + /** + * The certificate issuer's Subject Key Identifier + * + * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" + */ + certIssuerSKI: string; + /** + * The certificate's SHA-1 fingerprint + * + * @example "6b9109f323999e52259cda7373ff0b4d26bd232e" + */ + certFingerprintSHA1: string; + /** + * The certificate's SHA-256 fingerprint + * + * @example "acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea" + */ + certFingerprintSHA256: string; + /** + * The effective starting date of the certificate + * + * @example "Dec 22 19:39:00 2018 GMT" + */ + certNotBefore: string; + /** + * The effective expiration date of the certificate + * + * @example "Dec 22 19:39:00 2018 GMT" + */ + certNotAfter: string; +} +/** Placeholder values for TLS Client Authorization */ +interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder { + certPresented: "0"; + certVerified: "NONE"; + certRevoked: "0"; + certIssuerDN: ""; + certSubjectDN: ""; + certIssuerDNRFC2253: ""; + certSubjectDNRFC2253: ""; + certIssuerDNLegacy: ""; + certSubjectDNLegacy: ""; + certSerial: ""; + certIssuerSerial: ""; + certSKI: ""; + certIssuerSKI: ""; + certFingerprintSHA1: ""; + certFingerprintSHA256: ""; + certNotBefore: ""; + certNotAfter: ""; +} +/** Possible outcomes of TLS verification */ +declare type CertVerificationStatus = +/** Authentication succeeded */ +"SUCCESS" +/** No certificate was presented */ + | "NONE" +/** Failed because the certificate was self-signed */ + | "FAILED:self signed certificate" +/** Failed because the certificate failed a trust chain check */ + | "FAILED:unable to verify the first certificate" +/** Failed because the certificate not yet valid */ + | "FAILED:certificate is not yet valid" +/** Failed because the certificate is expired */ + | "FAILED:certificate has expired" +/** Failed for another unspecified reason */ + | "FAILED"; +/** + * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare. + */ +declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus = 0 /** Unknown */ | 1 /** no keepalives (not found) */ | 2 /** no connection re-use, opening keepalive connection failed */ | 3 /** no connection re-use, keepalive accepted and saved */ | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */ | 5; /** connection re-use, accepted by the origin server */ +/** ISO 3166-1 Alpha-2 codes */ +declare type Iso3166Alpha2Code = "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AQ" | "AR" | "AS" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FM" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GS" | "GT" | "GU" | "GW" | "GY" | "HK" | "HM" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM" | "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PR" | "PS" | "PT" | "PW" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "UM" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU" | "WF" | "WS" | "YE" | "YT" | "ZA" | "ZM" | "ZW"; +/** The 2-letter continent codes Cloudflare uses */ +declare type ContinentCode = "AF" | "AN" | "AS" | "EU" | "NA" | "OC" | "SA"; +type CfProperties = IncomingRequestCfProperties | RequestInitCfProperties; +interface D1Meta { + duration: number; + size_after: number; + rows_read: number; + rows_written: number; + last_row_id: number; + changed_db: boolean; + changes: number; +} +interface D1Response { + success: true; + meta: D1Meta & Record; + error?: never; +} +type D1Result = D1Response & { + results: T[]; +}; +interface D1ExecResult { + count: number; + duration: number; +} +type D1SessionConstraint = +// Indicates that the first query should go to the primary, and the rest queries +// using the same D1DatabaseSession will go to any replica that is consistent with +// the bookmark maintained by the session (returned by the first query). +"first-primary" +// Indicates that the first query can go anywhere (primary or replica), and the rest queries +// using the same D1DatabaseSession will go to any replica that is consistent with +// the bookmark maintained by the session (returned by the first query). + | "first-unconstrained"; +type D1SessionBookmark = string; +declare abstract class D1Database { + prepare(query: string): D1PreparedStatement; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise; + /** + * Creates a new D1 Session anchored at the given constraint or the bookmark. + * All queries executed using the created session will have sequential consistency, + * meaning that all writes done through the session will be visible in subsequent reads. + * + * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session. + */ + withSession(constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint): D1DatabaseSession; + /** + * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases. + */ + dump(): Promise; +} +declare abstract class D1DatabaseSession { + prepare(query: string): D1PreparedStatement; + batch(statements: D1PreparedStatement[]): Promise[]>; + /** + * @returns The latest session bookmark across all executed queries on the session. + * If no query has been executed yet, `null` is returned. + */ + getBookmark(): D1SessionBookmark | null; +} +declare abstract class D1PreparedStatement { + bind(...values: unknown[]): D1PreparedStatement; + first(colName: string): Promise; + first>(): Promise; + run>(): Promise>; + all>(): Promise>; + raw(options: { + columnNames: true; + }): Promise<[ + string[], + ...T[] + ]>; + raw(options?: { + columnNames?: false; + }): Promise; +} +// `Disposable` was added to TypeScript's standard lib types in version 5.2. +// To support older TypeScript versions, define an empty `Disposable` interface. +// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2, +// but this will ensure type checking on older versions still passes. +// TypeScript's interface merging will ensure our empty interface is effectively +// ignored when `Disposable` is included in the standard lib. +interface Disposable { +} +/** + * An email message that can be sent from a Worker. + */ +interface EmailMessage { + /** + * Envelope From attribute of the email message. + */ + readonly from: string; + /** + * Envelope To attribute of the email message. + */ + readonly to: string; +} +/** + * An email message that is sent to a consumer Worker and can be rejected/forwarded. + */ +interface ForwardableEmailMessage extends EmailMessage { + /** + * Stream of the email message content. + */ + readonly raw: ReadableStream; + /** + * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). + */ + readonly headers: Headers; + /** + * Size of the email message content. + */ + readonly rawSize: number; + /** + * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason. + * @param reason The reject reason. + * @returns void + */ + setReject(reason: string): void; + /** + * Forward this email message to a verified destination address of the account. + * @param rcptTo Verified destination address. + * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). + * @returns A promise that resolves when the email message is forwarded. + */ + forward(rcptTo: string, headers?: Headers): Promise; + /** + * Reply to the sender of this email message with a new EmailMessage object. + * @param message The reply message. + * @returns A promise that resolves when the email message is replied. + */ + reply(message: EmailMessage): Promise; +} +/** + * A binding that allows a Worker to send email messages. + */ +interface SendEmail { + send(message: EmailMessage): Promise; +} +declare abstract class EmailEvent extends ExtendableEvent { + readonly message: ForwardableEmailMessage; +} +declare type EmailExportedHandler = (message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext) => void | Promise; +declare module "cloudflare:email" { + let _EmailMessage: { + prototype: EmailMessage; + new (from: string, to: string, raw: ReadableStream | string): EmailMessage; + }; + export { _EmailMessage as EmailMessage }; +} +interface Hyperdrive { + /** + * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. + * + * Calling this method returns an idential socket to if you call + * `connect("host:port")` using the `host` and `port` fields from this object. + * Pick whichever approach works better with your preferred DB client library. + * + * Note that this socket is not yet authenticated -- it's expected that your + * code (or preferably, the client library of your choice) will authenticate + * using the information in this class's readonly fields. + */ + connect(): Socket; + /** + * A valid DB connection string that can be passed straight into the typical + * client library/driver/ORM. This will typically be the easiest way to use + * Hyperdrive. + */ + readonly connectionString: string; + /* + * A randomly generated hostname that is only valid within the context of the + * currently running Worker which, when passed into `connect()` function from + * the "cloudflare:sockets" module, will connect to the Hyperdrive instance + * for your database. + */ + readonly host: string; + /* + * The port that must be paired the the host field when connecting. + */ + readonly port: number; + /* + * The username to use when authenticating to your database via Hyperdrive. + * Unlike the host and password, this will be the same every time + */ + readonly user: string; + /* + * The randomly generated password to use when authenticating to your + * database via Hyperdrive. Like the host field, this password is only valid + * within the context of the currently running Worker instance from which + * it's read. + */ + readonly password: string; + /* + * The name of the database to connect to. + */ + readonly database: string; +} +// Copyright (c) 2024 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +type ImageInfoResponse = { + format: 'image/svg+xml'; +} | { + format: string; + fileSize: number; + width: number; + height: number; +}; +type ImageTransform = { + fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'; + gravity?: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | 'face' | { + x?: number; + y?: number; + mode: 'remainder' | 'box-center'; + }; + trim?: { + top?: number; + bottom?: number; + left?: number; + right?: number; + width?: number; + height?: number; + border?: boolean | { + color?: string; + tolerance?: number; + keep?: number; + }; + }; + width?: number; + height?: number; + background?: string; + rotate?: number; + sharpen?: number; + blur?: number; + contrast?: number; + brightness?: number; + gamma?: number; + border?: { + color?: string; + width?: number; + top?: number; + bottom?: number; + left?: number; + right?: number; + }; + zoom?: number; +}; +type ImageDrawOptions = { + opacity?: number; + repeat?: boolean | string; + top?: number; + left?: number; + bottom?: number; + right?: number; +}; +type ImageOutputOptions = { + format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba'; + quality?: number; + background?: string; +}; +interface ImagesBinding { + /** + * Get image metadata (type, width and height) + * @throws {@link ImagesError} with code 9412 if input is not an image + * @param stream The image bytes + */ + info(stream: ReadableStream): Promise; + /** + * Begin applying a series of transformations to an image + * @param stream The image bytes + * @returns A transform handle + */ + input(stream: ReadableStream): ImageTransformer; +} +interface ImageTransformer { + /** + * Apply transform next, returning a transform handle. + * You can then apply more transformations, draw, or retrieve the output. + * @param transform + */ + transform(transform: ImageTransform): ImageTransformer; + /** + * Draw an image on this transformer, returning a transform handle. + * You can then apply more transformations, draw, or retrieve the output. + * @param image The image (or transformer that will give the image) to draw + * @param options The options configuring how to draw the image + */ + draw(image: ReadableStream | ImageTransformer, options?: ImageDrawOptions): ImageTransformer; + /** + * Retrieve the image that results from applying the transforms to the + * provided input + * @param options Options that apply to the output e.g. output format + */ + output(options: ImageOutputOptions): Promise; +} +interface ImageTransformationResult { + /** + * The image as a response, ready to store in cache or return to users + */ + response(): Response; + /** + * The content type of the returned image + */ + contentType(): string; + /** + * The bytes of the response + */ + image(): ReadableStream; +} +interface ImagesError extends Error { + readonly code: number; + readonly message: string; + readonly stack?: string; +} +type Params

= Record; +type EventContext = { + request: Request>; + functionPath: string; + waitUntil: (promise: Promise) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise; + env: Env & { + ASSETS: { + fetch: typeof fetch; + }; + }; + params: Params

; + data: Data; +}; +type PagesFunction = Record> = (context: EventContext) => Response | Promise; +type EventPluginContext = { + request: Request>; + functionPath: string; + waitUntil: (promise: Promise) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise; + env: Env & { + ASSETS: { + fetch: typeof fetch; + }; + }; + params: Params

; + data: Data; + pluginArgs: PluginArgs; +}; +type PagesPluginFunction = Record, PluginArgs = unknown> = (context: EventPluginContext) => Response | Promise; +declare module "assets:*" { + export const onRequest: PagesFunction; +} +// Copyright (c) 2022-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +declare module "cloudflare:pipelines" { + export abstract class PipelineTransformationEntrypoint { + /** + * run recieves an array of PipelineRecord which can be + * mutated and returned to the pipeline + * @param records Incoming records from the pipeline to be transformed + * @param metadata Information about the specific pipeline calling the transformation entrypoint + * @returns A promise containing the transformed PipelineRecord array + */ + protected env: Env; + protected ctx: ExecutionContext; + constructor(ctx: ExecutionContext, env: Env); + public run(records: I[], metadata: PipelineBatchMetadata): Promise; + } + export type PipelineRecord = Record; + export type PipelineBatchMetadata = { + pipelineId: string; + pipelineName: string; + }; + export interface Pipeline { + /** + * The Pipeline interface represents the type of a binding to a Pipeline + * + * @param records The records to send to the pipeline + */ + send(records: T[]): Promise; + } +} +// PubSubMessage represents an incoming PubSub message. +// The message includes metadata about the broker, the client, and the payload +// itself. +// https://developers.cloudflare.com/pub-sub/ +interface PubSubMessage { + // Message ID + readonly mid: number; + // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT + readonly broker: string; + // The MQTT topic the message was sent on. + readonly topic: string; + // The client ID of the client that published this message. + readonly clientId: string; + // The unique identifier (JWT ID) used by the client to authenticate, if token + // auth was used. + readonly jti?: string; + // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker + // received the message from the client. + readonly receivedAt: number; + // An (optional) string with the MIME type of the payload, if set by the + // client. + readonly contentType: string; + // Set to 1 when the payload is a UTF-8 string + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063 + readonly payloadFormatIndicator: number; + // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays. + // You can use payloadFormatIndicator to inspect this before decoding. + payload: string | Uint8Array; +} +// JsonWebKey extended by kid parameter +interface JsonWebKeyWithKid extends JsonWebKey { + // Key Identifier of the JWK + readonly kid: string; +} +interface RateLimitOptions { + key: string; +} +interface RateLimitOutcome { + success: boolean; +} +interface RateLimit { + /** + * Rate limit a request based on the provided options. + * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/ + * @returns A promise that resolves with the outcome of the rate limit. + */ + limit(options: RateLimitOptions): Promise; +} +// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need +// to referenced by `Fetcher`. This is included in the "importable" version of the types which +// strips all `module` blocks. +declare namespace Rpc { + // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s. + // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`. + // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to + // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape) + export const __RPC_STUB_BRAND: "__RPC_STUB_BRAND"; + export const __RPC_TARGET_BRAND: "__RPC_TARGET_BRAND"; + export const __WORKER_ENTRYPOINT_BRAND: "__WORKER_ENTRYPOINT_BRAND"; + export const __DURABLE_OBJECT_BRAND: "__DURABLE_OBJECT_BRAND"; + export const __WORKFLOW_ENTRYPOINT_BRAND: "__WORKFLOW_ENTRYPOINT_BRAND"; + export interface RpcTargetBranded { + [__RPC_TARGET_BRAND]: never; + } + export interface WorkerEntrypointBranded { + [__WORKER_ENTRYPOINT_BRAND]: never; + } + export interface DurableObjectBranded { + [__DURABLE_OBJECT_BRAND]: never; + } + export interface WorkflowEntrypointBranded { + [__WORKFLOW_ENTRYPOINT_BRAND]: never; + } + export type EntrypointBranded = WorkerEntrypointBranded | DurableObjectBranded | WorkflowEntrypointBranded; + // Types that can be used through `Stub`s + export type Stubable = RpcTargetBranded | ((...args: any[]) => any); + // Types that can be passed over RPC + // The reason for using a generic type here is to build a serializable subset of structured + // cloneable composite types. This allows types defined with the "interface" keyword to pass the + // serializable check as well. Otherwise, only types defined with the "type" keyword would pass. + type Serializable = + // Structured cloneables + void | undefined | null | boolean | number | bigint | string | TypedArray | ArrayBuffer | DataView | Date | Error | RegExp + // Structured cloneable composites + | Map ? Serializable : never, T extends Map ? Serializable : never> | Set ? Serializable : never> | ReadonlyArray ? Serializable : never> | { + [K in keyof T]: K extends number | string ? Serializable : never; + } + // Special types + | ReadableStream | WritableStream | Request | Response | Headers | Stub + // Serialized as stubs, see `Stubify` + | Stubable; + // Base type for all RPC stubs, including common memory management methods. + // `T` is used as a marker type for unwrapping `Stub`s later. + interface StubBase extends Disposable { + [__RPC_STUB_BRAND]: T; + dup(): this; + } + export type Stub = Provider & StubBase; + // Recursively rewrite all `Stubable` types with `Stub`s + // prettier-ignore + type Stubify = T extends Stubable ? Stub : T extends Map ? Map, Stubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends { + [key: string | number]: any; + } ? { + [K in keyof T]: Stubify; + } : T; + // Recursively rewrite all `Stub`s with the corresponding `T`s. + // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies: + // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`. + // prettier-ignore + type Unstubify = T extends StubBase ? V : T extends Map ? Map, Unstubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends { + [key: string | number]: unknown; + } ? { + [K in keyof T]: Unstubify; + } : T; + type UnstubifyAll = { + [I in keyof A]: Unstubify; + }; + // Utility type for adding `Provider`/`Disposable`s to `object` types only. + // Note `unknown & T` is equivalent to `T`. + type MaybeProvider = T extends object ? Provider : unknown; + type MaybeDisposable = T extends object ? Disposable : unknown; + // Type for method return or property on an RPC interface. + // - Stubable types are replaced by stubs. + // - Serializable types are passed by value, with stubable types replaced by stubs + // and a top-level `Disposer`. + // Everything else can't be passed over PRC. + // Technically, we use custom thenables here, but they quack like `Promise`s. + // Intersecting with `(Maybe)Provider` allows pipelining. + // prettier-ignore + type Result = R extends Stubable ? Promise> & Provider : R extends Serializable ? Promise & MaybeDisposable> & MaybeProvider : never; + // Type for method or property on an RPC interface. + // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s. + // Unwrapping `Stub`s allows calling with `Stubable` arguments. + // For properties, rewrite types to be `Result`s. + // In each case, unwrap `Promise`s. + type MethodOrProperty = V extends (...args: infer P) => infer R ? (...args: UnstubifyAll

) => Result> : Result>; + // Type for the callable part of an `Provider` if `T` is callable. + // This is intersected with methods/properties. + type MaybeCallableProvider = T extends (...args: any[]) => any ? MethodOrProperty : unknown; + // Base type for all other types providing RPC-like interfaces. + // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types. + // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC. + export type Provider = MaybeCallableProvider & { + [K in Exclude>]: MethodOrProperty; + }; +} +declare namespace Cloudflare { + interface Env { + } +} +declare module "cloudflare:workers" { + export type RpcStub = Rpc.Stub; + export const RpcStub: { + new (value: T): Rpc.Stub; + }; + export abstract class RpcTarget implements Rpc.RpcTargetBranded { + [Rpc.__RPC_TARGET_BRAND]: never; + } + // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC + export abstract class WorkerEntrypoint implements Rpc.WorkerEntrypointBranded { + [Rpc.__WORKER_ENTRYPOINT_BRAND]: never; + protected ctx: ExecutionContext; + protected env: Env; + constructor(ctx: ExecutionContext, env: Env); + fetch?(request: Request): Response | Promise; + tail?(events: TraceItem[]): void | Promise; + trace?(traces: TraceItem[]): void | Promise; + scheduled?(controller: ScheduledController): void | Promise; + queue?(batch: MessageBatch): void | Promise; + test?(controller: TestController): void | Promise; + } + export abstract class DurableObject implements Rpc.DurableObjectBranded { + [Rpc.__DURABLE_OBJECT_BRAND]: never; + protected ctx: DurableObjectState; + protected env: Env; + constructor(ctx: DurableObjectState, env: Env); + fetch?(request: Request): Response | Promise; + alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; + webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; + webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; + webSocketError?(ws: WebSocket, error: unknown): void | Promise; + } + export type WorkflowDurationLabel = "second" | "minute" | "hour" | "day" | "week" | "month" | "year"; + export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${"s" | ""}` | number; + export type WorkflowDelayDuration = WorkflowSleepDuration; + export type WorkflowTimeoutDuration = WorkflowSleepDuration; + export type WorkflowBackoff = "constant" | "linear" | "exponential"; + export type WorkflowStepConfig = { + retries?: { + limit: number; + delay: WorkflowDelayDuration | number; + backoff?: WorkflowBackoff; + }; + timeout?: WorkflowTimeoutDuration | number; + }; + export type WorkflowEvent = { + payload: Readonly; + timestamp: Date; + instanceId: string; + }; + export abstract class WorkflowStep { + do>(name: string, callback: () => Promise): Promise; + do>(name: string, config: WorkflowStepConfig, callback: () => Promise): Promise; + sleep: (name: string, duration: WorkflowSleepDuration) => Promise; + sleepUntil: (name: string, timestamp: Date | number) => Promise; + } + export abstract class WorkflowEntrypoint | unknown = unknown> implements Rpc.WorkflowEntrypointBranded { + [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never; + protected ctx: ExecutionContext; + protected env: Env; + constructor(ctx: ExecutionContext, env: Env); + run(event: Readonly>, step: WorkflowStep): Promise; + } + export const env: Cloudflare.Env; +} +declare module "cloudflare:sockets" { + function _connect(address: string | SocketAddress, options?: SocketOptions): Socket; + export { _connect as connect }; +} +declare namespace TailStream { + interface Header { + readonly name: string; + readonly value: string; + } + interface FetchEventInfo { + readonly type: "fetch"; + readonly method: string; + readonly url: string; + readonly cfJson: string; + readonly headers: Header[]; + } + interface JsRpcEventInfo { + readonly type: "jsrpc"; + readonly methodName: string; + } + interface ScheduledEventInfo { + readonly type: "scheduled"; + readonly scheduledTime: Date; + readonly cron: string; + } + interface AlarmEventInfo { + readonly type: "alarm"; + readonly scheduledTime: Date; + } + interface QueueEventInfo { + readonly type: "queue"; + readonly queueName: string; + readonly batchSize: number; + } + interface EmailEventInfo { + readonly type: "email"; + readonly mailFrom: string; + readonly rcptTo: string; + readonly rawSize: number; + } + interface TraceEventInfo { + readonly type: "trace"; + readonly traces: (string | null)[]; + } + interface HibernatableWebSocketEventInfoMessage { + readonly type: "message"; + } + interface HibernatableWebSocketEventInfoError { + readonly type: "error"; + } + interface HibernatableWebSocketEventInfoClose { + readonly type: "close"; + readonly code: number; + readonly wasClean: boolean; + } + interface HibernatableWebSocketEventInfo { + readonly type: "hibernatableWebSocket"; + readonly info: HibernatableWebSocketEventInfoClose | HibernatableWebSocketEventInfoError | HibernatableWebSocketEventInfoMessage; + } + interface Resume { + readonly type: "resume"; + readonly attachment?: any; + } + interface CustomEventInfo { + readonly type: "custom"; + } + interface FetchResponseInfo { + readonly type: "fetch"; + readonly statusCode: number; + } + type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound"; + interface ScriptVersion { + readonly id: string; + readonly tag?: string; + readonly message?: string; + } + interface Trigger { + readonly traceId: string; + readonly invocationId: string; + readonly spanId: string; + } + interface Onset { + readonly type: "onset"; + readonly dispatchNamespace?: string; + readonly entrypoint?: string; + readonly scriptName?: string; + readonly scriptTags?: string[]; + readonly scriptVersion?: ScriptVersion; + readonly trigger?: Trigger; + readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | Resume | CustomEventInfo; + } + interface Outcome { + readonly type: "outcome"; + readonly outcome: EventOutcome; + readonly cpuTime: number; + readonly wallTime: number; + } + interface Hibernate { + readonly type: "hibernate"; + } + interface SpanOpen { + readonly type: "spanOpen"; + readonly op?: string; + readonly info?: FetchEventInfo | JsRpcEventInfo | Attribute[]; + } + interface SpanClose { + readonly type: "spanClose"; + readonly outcome: EventOutcome; + } + interface DiagnosticChannelEvent { + readonly type: "diagnosticChannel"; + readonly channel: string; + readonly message: any; + } + interface Exception { + readonly type: "exception"; + readonly name: string; + readonly message: string; + readonly stack?: string; + } + interface Log { + readonly type: "log"; + readonly level: "debug" | "error" | "info" | "log" | "warn"; + readonly message: string; + } + interface Return { + readonly type: "return"; + readonly info?: FetchResponseInfo | Attribute[]; + } + interface Link { + readonly type: "link"; + readonly label?: string; + readonly traceId: string; + readonly invocationId: string; + readonly spanId: string; + } + interface Attribute { + readonly type: "attribute"; + readonly name: string; + readonly value: string | string[] | boolean | boolean[] | number | number[]; + } + type Mark = DiagnosticChannelEvent | Exception | Log | Return | Link | Attribute[]; + interface TailEvent { + readonly traceId: string; + readonly invocationId: string; + readonly spanId: string; + readonly timestamp: Date; + readonly sequence: number; + readonly event: Onset | Outcome | Hibernate | SpanOpen | SpanClose | Mark; + } + type TailEventHandler = (event: TailEvent) => void | Promise; + type TailEventHandlerName = "onset" | "outcome" | "hibernate" | "spanOpen" | "spanClose" | "diagnosticChannel" | "exception" | "log" | "return" | "link" | "attribute"; + type TailEventHandlerObject = Record; + type TailEventHandlerType = TailEventHandler | TailEventHandlerObject; +} +// Copyright (c) 2022-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +/** + * Data types supported for holding vector metadata. + */ +type VectorizeVectorMetadataValue = string | number | boolean | string[]; +/** + * Additional information to associate with a vector. + */ +type VectorizeVectorMetadata = VectorizeVectorMetadataValue | Record; +type VectorFloatArray = Float32Array | Float64Array; +interface VectorizeError { + code?: number; + error: string; +} +/** + * Comparison logic/operation to use for metadata filtering. + * + * This list is expected to grow as support for more operations are released. + */ +type VectorizeVectorMetadataFilterOp = "$eq" | "$ne"; +/** + * Filter criteria for vector metadata used to limit the retrieved query result set. + */ +type VectorizeVectorMetadataFilter = { + [field: string]: Exclude | null | { + [Op in VectorizeVectorMetadataFilterOp]?: Exclude | null; + }; +}; +/** + * Supported distance metrics for an index. + * Distance metrics determine how other "similar" vectors are determined. + */ +type VectorizeDistanceMetric = "euclidean" | "cosine" | "dot-product"; +/** + * Metadata return levels for a Vectorize query. + * + * Default to "none". + * + * @property all Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data. + * @property indexed Return all metadata fields configured for indexing in the vector return set. This level of retrieval is "free" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings). + * @property none No indexed metadata will be returned. + */ +type VectorizeMetadataRetrievalLevel = "all" | "indexed" | "none"; +interface VectorizeQueryOptions { + topK?: number; + namespace?: string; + returnValues?: boolean; + returnMetadata?: boolean | VectorizeMetadataRetrievalLevel; + filter?: VectorizeVectorMetadataFilter; +} +/** + * Information about the configuration of an index. + */ +type VectorizeIndexConfig = { + dimensions: number; + metric: VectorizeDistanceMetric; +} | { + preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity +}; +/** + * Metadata about an existing index. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link VectorizeIndexInfo} for its post-beta equivalent. + */ +interface VectorizeIndexDetails { + /** The unique ID of the index */ + readonly id: string; + /** The name of the index. */ + name: string; + /** (optional) A human readable description for the index. */ + description?: string; + /** The index configuration, including the dimension size and distance metric. */ + config: VectorizeIndexConfig; + /** The number of records containing vectors within the index. */ + vectorsCount: number; +} +/** + * Metadata about an existing index. + */ +interface VectorizeIndexInfo { + /** The number of records containing vectors within the index. */ + vectorCount: number; + /** Number of dimensions the index has been configured for. */ + dimensions: number; + /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */ + processedUpToDatetime: number; + /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */ + processedUpToMutation: number; +} +/** + * Represents a single vector value set along with its associated metadata. + */ +interface VectorizeVector { + /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */ + id: string; + /** The vector values */ + values: VectorFloatArray | number[]; + /** The namespace this vector belongs to. */ + namespace?: string; + /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */ + metadata?: Record; +} +/** + * Represents a matched vector for a query along with its score and (if specified) the matching vector information. + */ +type VectorizeMatch = Pick, "values"> & Omit & { + /** The score or rank for similarity, when returned as a result */ + score: number; +}; +/** + * A set of matching {@link VectorizeMatch} for a particular query. + */ +interface VectorizeMatches { + matches: VectorizeMatch[]; + count: number; +} +/** + * Results of an operation that performed a mutation on a set of vectors. + * Here, `ids` is a list of vectors that were successfully processed. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link VectorizeAsyncMutation} for its post-beta equivalent. + */ +interface VectorizeVectorMutation { + /* List of ids of vectors that were successfully processed. */ + ids: string[]; + /* Total count of the number of processed vectors. */ + count: number; +} +/** + * Result type indicating a mutation on the Vectorize Index. + * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation. + */ +interface VectorizeAsyncMutation { + /** The unique identifier for the async mutation operation containing the changeset. */ + mutationId: string; +} +/** + * A Vectorize Vector Search Index for querying vectors/embeddings. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link Vectorize} for its new implementation. + */ +declare abstract class VectorizeIndex { + /** + * Get information about the currently bound index. + * @returns A promise that resolves with information about the current index. + */ + public describe(): Promise; + /** + * Use the provided vector to perform a similarity search across the index. + * @param vector Input vector that will be used to drive the similarity search. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; + /** + * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. + * @param vectors List of vectors that will be inserted. + * @returns A promise that resolves with the ids & count of records that were successfully processed. + */ + public insert(vectors: VectorizeVector[]): Promise; + /** + * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. + * @param vectors List of vectors that will be upserted. + * @returns A promise that resolves with the ids & count of records that were successfully processed. + */ + public upsert(vectors: VectorizeVector[]): Promise; + /** + * Delete a list of vectors with a matching id. + * @param ids List of vector ids that should be deleted. + * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted). + */ + public deleteByIds(ids: string[]): Promise; + /** + * Get a list of vectors with a matching id. + * @param ids List of vector ids that should be returned. + * @returns A promise that resolves with the raw unscored vectors matching the id set. + */ + public getByIds(ids: string[]): Promise; +} +/** + * A Vectorize Vector Search Index for querying vectors/embeddings. + * + * Mutations in this version are async, returning a mutation id. + */ +declare abstract class Vectorize { + /** + * Get information about the currently bound index. + * @returns A promise that resolves with information about the current index. + */ + public describe(): Promise; + /** + * Use the provided vector to perform a similarity search across the index. + * @param vector Input vector that will be used to drive the similarity search. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; + /** + * Use the provided vector-id to perform a similarity search across the index. + * @param vectorId Id for a vector in the index against which the index should be queried. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public queryById(vectorId: string, options?: VectorizeQueryOptions): Promise; + /** + * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. + * @param vectors List of vectors that will be inserted. + * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset. + */ + public insert(vectors: VectorizeVector[]): Promise; + /** + * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. + * @param vectors List of vectors that will be upserted. + * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset. + */ + public upsert(vectors: VectorizeVector[]): Promise; + /** + * Delete a list of vectors with a matching id. + * @param ids List of vector ids that should be deleted. + * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset. + */ + public deleteByIds(ids: string[]): Promise; + /** + * Get a list of vectors with a matching id. + * @param ids List of vector ids that should be returned. + * @returns A promise that resolves with the raw unscored vectors matching the id set. + */ + public getByIds(ids: string[]): Promise; +} +/** + * The interface for "version_metadata" binding + * providing metadata about the Worker Version using this binding. + */ +type WorkerVersionMetadata = { + /** The ID of the Worker Version using this binding */ + id: string; + /** The tag of the Worker Version using this binding */ + tag: string; + /** The timestamp of when the Worker Version was uploaded */ + timestamp: string; +}; +interface DynamicDispatchLimits { + /** + * Limit CPU time in milliseconds. + */ + cpuMs?: number; + /** + * Limit number of subrequests. + */ + subRequests?: number; +} +interface DynamicDispatchOptions { + /** + * Limit resources of invoked Worker script. + */ + limits?: DynamicDispatchLimits; + /** + * Arguments for outbound Worker script, if configured. + */ + outbound?: { + [key: string]: any; + }; +} +interface DispatchNamespace { + /** + * @param name Name of the Worker script. + * @param args Arguments to Worker script. + * @param options Options for Dynamic Dispatch invocation. + * @returns A Fetcher object that allows you to send requests to the Worker script. + * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown. + */ + get(name: string, args?: { + [key: string]: any; + }, options?: DynamicDispatchOptions): Fetcher; +} +declare module "cloudflare:workflows" { + /** + * NonRetryableError allows for a user to throw a fatal error + * that makes a Workflow instance fail immediately without triggering a retry + */ + export class NonRetryableError extends Error { + public constructor(message: string, name?: string); + } +} +declare abstract class Workflow { + /** + * Get a handle to an existing instance of the Workflow. + * @param id Id for the instance of this Workflow + * @returns A promise that resolves with a handle for the Instance + */ + public get(id: string): Promise; + /** + * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown. + * @param options Options when creating an instance including id and params + * @returns A promise that resolves with a handle for the Instance + */ + public create(options?: WorkflowInstanceCreateOptions): Promise; + /** + * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown. + * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached. + * @param batch List of Options when creating an instance including name and params + * @returns A promise that resolves with a list of handles for the created instances. + */ + public createBatch(batch: WorkflowInstanceCreateOptions[]): Promise; +} +interface WorkflowInstanceCreateOptions { + /** + * An id for your Workflow instance. Must be unique within the Workflow. + */ + id?: string; + /** + * The event payload the Workflow instance is triggered with + */ + params?: PARAMS; +} +type InstanceStatus = { + status: "queued" // means that instance is waiting to be started (see concurrency limits) + | "running" | "paused" | "errored" | "terminated" // user terminated the instance while it was running + | "complete" | "waiting" // instance is hibernating and waiting for sleep or event to finish + | "waitingForPause" // instance is finishing the current work to pause + | "unknown"; + error?: string; + output?: object; +}; +interface WorkflowError { + code?: number; + message: string; +} +declare abstract class WorkflowInstance { + public id: string; + /** + * Pause the instance. + */ + public pause(): Promise; + /** + * Resume the instance. If it is already running, an error will be thrown. + */ + public resume(): Promise; + /** + * Terminate the instance. If it is errored, terminated or complete, an error will be thrown. + */ + public terminate(): Promise; + /** + * Restart the instance. + */ + public restart(): Promise; + /** + * Returns the current status of the instance. + */ + public status(): Promise; +} diff --git a/packages/mcp-server/cloudflare-worker/wrangler.jsonc b/packages/mcp-server/cloudflare-worker/wrangler.jsonc new file mode 100644 index 00000000..a49004af --- /dev/null +++ b/packages/mcp-server/cloudflare-worker/wrangler.jsonc @@ -0,0 +1,35 @@ +/** + * For more details on how to configure Wrangler, refer to: + * https://developers.cloudflare.com/workers/wrangler/configuration/ + */ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "imagekit-nodejs-api-mcp-server", + "main": "src/index.ts", + "compatibility_date": "2025-03-10", + "compatibility_flags": ["nodejs_compat"], + "migrations": [ + { + "new_sqlite_classes": ["MyMCP"], + "tag": "v1" + } + ], + "durable_objects": { + "bindings": [ + { + "class_name": "MyMCP", + "name": "MCP_OBJECT" + } + ] + }, + "kv_namespaces": [ + { + "binding": "OAUTH_KV", + "id": "ae6fe7d7993146a9b8d54d87f73b0bdf" + } + ], + "observability": { + "enabled": true + }, + "assets": { "directory": "./static/", "binding": "ASSETS" } +} diff --git a/packages/mcp-server/jest.config.ts b/packages/mcp-server/jest.config.ts new file mode 100644 index 00000000..2744c3ba --- /dev/null +++ b/packages/mcp-server/jest.config.ts @@ -0,0 +1,17 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], + }, + moduleNameMapper: { + '^imagekit-api-mcp$': '/src/index.ts', + '^imagekit-api-mcp/(.*)$': '/src/$1', + }, + modulePathIgnorePatterns: ['/dist/'], + testPathIgnorePatterns: ['scripts'], +}; + +export default config; diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json new file mode 100644 index 00000000..37d0121d --- /dev/null +++ b/packages/mcp-server/manifest.json @@ -0,0 +1,57 @@ +{ + "dxt_version": "0.2", + "name": "imagekit-api-mcp", + "version": "0.0.1-alpha.0", + "description": "The official MCP Server for the Image Kit API", + "author": { + "name": "Image Kit", + "email": "developer@imagekit.io" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git" + }, + "homepage": "https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server#readme", + "documentation": "https://imagekit.io/docs", + "server": { + "type": "node", + "entry_point": "index.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/index.js"], + "env": { + "IMAGEKIT_PRIVATE_API_KEY": "${user_config.IMAGEKIT_PRIVATE_API_KEY}", + "OPTIONAL_IMAGEKIT_IGNORES_THIS": "${user_config.OPTIONAL_IMAGEKIT_IGNORES_THIS}", + "IMAGEKIT_WEBHOOK_SECRET": "${user_config.IMAGEKIT_WEBHOOK_SECRET}" + } + } + }, + "user_config": { + "IMAGEKIT_PRIVATE_API_KEY": { + "title": "private_key", + "description": "Your ImageKit private API key (starts with `private_`).\nYou can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).\n", + "required": true, + "type": "string" + }, + "OPTIONAL_IMAGEKIT_IGNORES_THIS": { + "title": "password", + "description": "Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password.\nThe password field is automatically managed by the SDK and should not be set.\n", + "required": false, + "type": "string" + }, + "IMAGEKIT_WEBHOOK_SECRET": { + "title": "webhook_secret", + "description": "Your ImageKit webhook secret used by the SDK to verify webhook signatures for security.\nThis secret starts with a `whsec_` prefix and is essential for webhook verification.\nYou can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\n\n**Security Note**: Treat this secret like a password - keep it private and never expose it publicly.\nThis field is optional and only required if you plan to use webhook signature verification.\nLearn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature).\n", + "required": false, + "type": "string" + } + }, + "tools": [], + "tools_generated": true, + "compatibility": { + "runtimes": { + "node": ">=18.0.0" + } + }, + "keywords": ["api"] +} diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json new file mode 100644 index 00000000..788cd893 --- /dev/null +++ b/packages/mcp-server/package.json @@ -0,0 +1,86 @@ +{ + "name": "imagekit-api-mcp", + "version": "0.0.1-alpha.0", + "description": "The official MCP Server for the Image Kit API", + "author": "Image Kit ", + "types": "dist/index.d.ts", + "main": "dist/index.js", + "type": "commonjs", + "repository": { + "type": "git", + "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git", + "directory": "packages/mcp-server" + }, + "homepage": "https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server#readme", + "license": "Apache-2.0", + "packageManager": "yarn@1.22.22", + "private": false, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "build": "bash ./build", + "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1", + "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", + "format": "prettier --write --cache --cache-strategy metadata . !dist", + "prepare": "npm run build", + "tsn": "ts-node -r tsconfig-paths/register", + "lint": "eslint --ext ts,js .", + "fix": "eslint --fix --ext ts,js ." + }, + "dependencies": { + "@imagekit/nodejs": "file:../../dist/", + "@cloudflare/cabidela": "^0.2.4", + "@modelcontextprotocol/sdk": "^1.11.5", + "@valtown/deno-http-worker": "^0.0.21", + "cors": "^2.8.5", + "express": "^5.1.0", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "qs": "^6.14.0", + "yargs": "^17.7.2", + "zod": "^3.25.20", + "zod-to-json-schema": "^3.24.5", + "zod-validation-error": "^4.0.1" + }, + "bin": { + "mcp-server": "dist/index.js" + }, + "devDependencies": { + "@anthropic-ai/mcpb": "^1.1.0", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jest": "^29.4.0", + "@types/qs": "^6.14.0", + "@types/yargs": "^17.0.8", + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "eslint": "^8.49.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "jest": "^29.4.0", + "prettier": "^3.0.0", + "ts-jest": "^29.1.0", + "ts-morph": "^19.0.0", + "ts-node": "^10.5.0", + "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", + "tsconfig-paths": "^4.0.0", + "typescript": "5.8.3" + }, + "imports": { + "imagekit-api-mcp": ".", + "imagekit-api-mcp/*": "./src/*" + }, + "exports": { + ".": { + "require": "./dist/index.js", + "default": "./dist/index.mjs" + }, + "./*.mjs": "./dist/*.mjs", + "./*.js": "./dist/*.js", + "./*": { + "require": "./dist/*.js", + "default": "./dist/*.mjs" + } + } +} diff --git a/packages/mcp-server/scripts/copy-bundle-files.cjs b/packages/mcp-server/scripts/copy-bundle-files.cjs new file mode 100644 index 00000000..a61be6b8 --- /dev/null +++ b/packages/mcp-server/scripts/copy-bundle-files.cjs @@ -0,0 +1,36 @@ +const fs = require('fs'); +const path = require('path'); +const pkgJson = require('../dist-bundle/package.json'); + +const distDir = path.resolve(__dirname, '..', 'dist'); +const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle'); +const distBundlePkgJson = path.join(distBundleDir, 'package.json'); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +async function copyFiles() { + // copy runtime files + for await (const file of walk(distDir)) { + if (!/[cm]?js$/.test(file)) continue; + const dest = path.join(distBundleDir, path.relative(distDir, file)); + await fs.promises.mkdir(path.dirname(dest), { recursive: true }); + await fs.promises.copyFile(file, dest); + } + + // replace package.json reference with local reference + for (const dep in pkgJson.dependencies) { + if (dep === '@imagekit/nodejs') { + pkgJson.dependencies[dep] = 'file:../../../dist/'; + } + } + + await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2)); +} + +copyFiles(); diff --git a/packages/mcp-server/scripts/postprocess-dist-package-json.cjs b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs new file mode 100644 index 00000000..2c75a6cd --- /dev/null +++ b/packages/mcp-server/scripts/postprocess-dist-package-json.cjs @@ -0,0 +1,12 @@ +const fs = require('fs'); +const pkgJson = require('../dist/package.json'); +const parentPkgJson = require('../../../package.json'); + +for (const dep in pkgJson.dependencies) { + // ensure we point to NPM instead of a local directory + if (dep === '@imagekit/nodejs') { + pkgJson.dependencies[dep] = '^' + parentPkgJson.version; + } +} + +fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2)); diff --git a/packages/mcp-server/src/code-tool-paths.cts b/packages/mcp-server/src/code-tool-paths.cts new file mode 100644 index 00000000..15ce7f55 --- /dev/null +++ b/packages/mcp-server/src/code-tool-paths.cts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export const workerPath = require.resolve('./code-tool-worker.mjs'); diff --git a/packages/mcp-server/src/code-tool-types.ts b/packages/mcp-server/src/code-tool-types.ts new file mode 100644 index 00000000..02e7e890 --- /dev/null +++ b/packages/mcp-server/src/code-tool-types.ts @@ -0,0 +1,14 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { ClientOptions } from '@imagekit/nodejs'; + +export type WorkerInput = { + opts: ClientOptions; + code: string; +}; +export type WorkerSuccess = { + result: unknown | null; + logLines: string[]; + errLines: string[]; +}; +export type WorkerError = { message: string | undefined }; diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts new file mode 100644 index 00000000..865c3928 --- /dev/null +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -0,0 +1,46 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import util from 'node:util'; +import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; +import { ImageKit } from '@imagekit/nodejs'; + +const fetch = async (req: Request): Promise => { + const { opts, code } = (await req.json()) as WorkerInput; + const client = new ImageKit({ + ...opts, + }); + + const logLines: string[] = []; + const errLines: string[] = []; + const console = { + log: (...args: unknown[]) => { + logLines.push(util.format(...args)); + }, + error: (...args: unknown[]) => { + errLines.push(util.format(...args)); + }, + }; + try { + let run_ = async (client: any) => {}; + eval(` + ${code} + run_ = run; + `); + const result = await run_(client); + return Response.json({ + result, + logLines, + errLines, + } satisfies WorkerSuccess); + } catch (e) { + const message = e instanceof Error ? e.message : undefined; + return Response.json( + { + message, + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } +}; + +export default { fetch }; diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts new file mode 100644 index 00000000..47d2064a --- /dev/null +++ b/packages/mcp-server/src/code-tool.ts @@ -0,0 +1,148 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { dirname } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import ImageKit, { ClientOptions } from '@imagekit/nodejs'; +import { Endpoint, ContentBlock, Metadata } from './tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; + +/** + * A tool that runs code against a copy of the SDK. + * + * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export async function codeTool(): Promise { + const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; + const tool: Tool = { + name: 'execute', + description: + 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, + }; + + // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. + const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); + const { workerPath } = await import('./code-tool-paths.cjs'); + + const handler = async (client: ImageKit, args: unknown) => { + const baseURLHostname = new URL(client.baseURL).hostname; + const { code } = args as { code: string }; + + const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), { + runFlags: [ + `--node-modules-dir=manual`, + `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`, + `--allow-net=${baseURLHostname}`, + // Allow environment variables because instantiating the client will try to read from them, + // even though they are not set. + '--allow-env', + ], + printOutput: true, + spawnOptions: { + cwd: dirname(workerPath), + }, + }); + + try { + const resp = await new Promise((resolve, reject) => { + worker.addEventListener('exit', (exitCode) => { + reject(new Error(`Worker exited with code ${exitCode}`)); + }); + + const opts: ClientOptions = { + baseURL: client.baseURL, + privateKey: client.privateKey, + password: client.password, + webhookSecret: client.webhookSecret, + defaultHeaders: { + 'X-Stainless-MCP': 'true', + }, + }; + + const req = worker.request( + 'http://localhost', + { + headers: { + 'content-type': 'application/json', + }, + method: 'POST', + }, + (resp) => { + const body: Uint8Array[] = []; + resp.on('error', (err) => { + reject(err); + }); + resp.on('data', (chunk) => { + body.push(chunk); + }); + resp.on('end', () => { + resolve( + new Response(Buffer.concat(body).toString(), { + status: resp.statusCode ?? 200, + headers: resp.headers as any, + }), + ); + }); + }, + ); + + const body = JSON.stringify({ + opts, + code, + } satisfies WorkerInput); + + req.write(body, (err) => { + if (err !== null && err !== undefined) { + reject(err); + } + }); + + req.end(); + }); + + if (resp.status === 200) { + const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; + const returnOutput: ContentBlock | null = + result === null ? null + : result === undefined ? null + : { + type: 'text', + text: typeof result === 'string' ? (result as string) : JSON.stringify(result), + }; + const logOutput: ContentBlock | null = + logLines.length === 0 ? + null + : { + type: 'text', + text: logLines.join('\n'), + }; + const errOutput: ContentBlock | null = + errLines.length === 0 ? + null + : { + type: 'text', + text: 'Error output:\n' + errLines.join('\n'), + }; + return { + content: [returnOutput, logOutput, errOutput].filter((block) => block !== null), + }; + } else { + const { message } = (await resp.json()) as WorkerError; + throw new Error(message); + } + } catch (e) { + throw e; + } finally { + worker.terminate(); + } + }; + + return { metadata, tool, handler }; +} diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts new file mode 100644 index 00000000..f84053c7 --- /dev/null +++ b/packages/mcp-server/src/compat.ts @@ -0,0 +1,483 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; +import { Endpoint } from './tools'; + +export interface ClientCapabilities { + topLevelUnions: boolean; + validJson: boolean; + refs: boolean; + unions: boolean; + formats: boolean; + toolNameLength: number | undefined; +} + +export const defaultClientCapabilities: ClientCapabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, +}; + +export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); +export type ClientType = z.infer; + +// Client presets for compatibility +// Note that these could change over time as models get better, so this is +// a best effort. +export const knownClients: Record, ClientCapabilities> = { + 'openai-agents': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + claude: { + topLevelUnions: true, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + 'claude-code': { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + cursor: { + topLevelUnions: false, + validJson: true, + refs: false, + unions: false, + formats: false, + toolNameLength: 50, + }, +}; + +/** + * Attempts to parse strings into JSON objects + */ +export function parseEmbeddedJSON(args: Record, schema: Record) { + let updated = false; + const newArgs: Record = Object.assign({}, args); + + for (const [key, value] of Object.entries(newArgs)) { + if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + // Only parse if result is a plain object (not array, null, or primitive) + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + newArgs[key] = parsed; + updated = true; + } + } catch (e) { + // Not valid JSON, leave as is + } + } + } + + if (updated) { + return newArgs; + } + + return args; +} + +export type JSONSchema = { + type?: string; + properties?: Record; + required?: string[]; + anyOf?: JSONSchema[]; + $ref?: string; + $defs?: Record; + [key: string]: any; +}; + +/** + * Truncates tool names to the specified length while ensuring uniqueness. + * If truncation would cause duplicate names, appends a number to make them unique. + */ +export function truncateToolNames(names: string[], maxLength: number): Map { + if (maxLength <= 0) { + return new Map(); + } + + const renameMap = new Map(); + const usedNames = new Set(); + + const toTruncate = names.filter((name) => name.length > maxLength); + + if (toTruncate.length === 0) { + return renameMap; + } + + const willCollide = + new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; + + if (!willCollide) { + for (const name of toTruncate) { + const truncatedName = name.slice(0, maxLength); + renameMap.set(name, truncatedName); + } + } else { + const baseLength = maxLength - 1; + + for (const name of toTruncate) { + const baseName = name.slice(0, baseLength); + let counter = 1; + + while (usedNames.has(baseName + counter)) { + counter++; + } + + const finalName = baseName + counter; + renameMap.set(name, finalName); + usedNames.add(finalName); + } + } + + return renameMap; +} + +/** + * Removes top-level unions from a tool by splitting it into multiple tools, + * one for each variant in the union. + */ +export function removeTopLevelUnions(tool: Tool): Tool[] { + const inputSchema = tool.inputSchema as JSONSchema; + const variants = inputSchema.anyOf; + + if (!variants || !Array.isArray(variants) || variants.length === 0) { + return [tool]; + } + + const defs = inputSchema.$defs || {}; + + return variants.map((variant, index) => { + const variantSchema: JSONSchema = { + ...inputSchema, + ...variant, + type: 'object', + properties: { + ...(inputSchema.properties || {}), + ...(variant.properties || {}), + }, + }; + + delete variantSchema.anyOf; + + if (!variantSchema['description']) { + variantSchema['description'] = tool.description; + } + + const usedDefs = findUsedDefs(variant, defs); + if (Object.keys(usedDefs).length > 0) { + variantSchema.$defs = usedDefs; + } else { + delete variantSchema.$defs; + } + + return { + ...tool, + name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, + description: variant['description'] || tool.description, + inputSchema: variantSchema, + } as Tool; + }); +} + +function findUsedDefs( + schema: JSONSchema, + defs: Record, + visited: Set = new Set(), +): Record { + const usedDefs: Record = {}; + + if (typeof schema !== 'object' || schema === null) { + return usedDefs; + } + + if (schema.$ref) { + const refParts = schema.$ref.split('/'); + if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { + const defName = refParts[2]; + const def = defs[defName]; + if (def && !visited.has(schema.$ref)) { + usedDefs[defName] = def; + visited.add(schema.$ref); + Object.assign(usedDefs, findUsedDefs(def, defs, visited)); + visited.delete(schema.$ref); + } + } + return usedDefs; + } + + for (const key in schema) { + if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { + Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); + } + } + + return usedDefs; +} + +// Export for testing +export { findUsedDefs }; + +/** + * Inlines all $refs in a schema, eliminating $defs. + * If a circular reference is detected, the circular property is removed. + */ +export function inlineRefs(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + const clonedSchema = { ...schema }; + const defs: Record = schema.$defs || {}; + + delete clonedSchema.$defs; + + const result = inlineRefsRecursive(clonedSchema, defs, new Set()); + // The top level can never be null + return result === null ? {} : result; +} + +function inlineRefsRecursive( + schema: JSONSchema, + defs: Record, + refPath: Set, +): JSONSchema | null { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => { + const processed = inlineRefsRecursive(item, defs, refPath); + return processed === null ? {} : processed; + }) as JSONSchema; + } + + const result = { ...schema }; + + if ('$ref' in result && typeof result.$ref === 'string') { + if (result.$ref.startsWith('#/$defs/')) { + const refName = result.$ref.split('/').pop() as string; + const def = defs[refName]; + + // If we've already seen this ref in our path, we have a circular reference + if (refPath.has(result.$ref)) { + // For circular references, we completely remove the property + // by returning null. The parent will remove it. + return null; + } + + if (def) { + const newRefPath = new Set(refPath); + newRefPath.add(result.$ref); + + const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); + + if (inlinedDef === null) { + return { ...result }; + } + + // Merge the inlined definition with the original schema's properties + // but preserve things like description, etc. + const { $ref, ...rest } = result; + return { ...inlinedDef, ...rest }; + } + } + + // Keep external refs as-is + return result; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); + if (processed === null) { + // Remove properties that would cause circular references + delete result[key]; + } else { + result[key] = processed; + } + } + } + + return result; +} + +/** + * Removes anyOf fields from a schema, using only the first variant. + */ +export function removeAnyOf(schema: JSONSchema): JSONSchema { + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeAnyOf(item)) as JSONSchema; + } + + const result = { ...schema }; + + if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { + const firstVariant = result.anyOf[0]; + + if (firstVariant && typeof firstVariant === 'object') { + // Special handling for properties to ensure deep merge + if (firstVariant.properties && result.properties) { + result.properties = { + ...result.properties, + ...(firstVariant.properties as Record), + }; + } else if (firstVariant.properties) { + result.properties = { ...firstVariant.properties }; + } + + for (const key in firstVariant) { + if (key !== 'properties') { + result[key] = firstVariant[key]; + } + } + } + + delete result.anyOf; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeAnyOf(result[key] as JSONSchema); + } + } + + return result; +} + +/** + * Removes format fields from a schema and appends them to the description. + */ +export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { + if (formatsCapability) { + return schema; + } + + if (!schema || typeof schema !== 'object') { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; + } + + const result = { ...schema }; + + if ('format' in result && typeof result['format'] === 'string') { + const formatStr = `(format: "${result['format']}")`; + + if ('description' in result && typeof result['description'] === 'string') { + result['description'] = `${result['description']} ${formatStr}`; + } else { + result['description'] = formatStr; + } + + delete result['format']; + } + + for (const key in result) { + if (result[key] && typeof result[key] === 'object') { + result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); + } + } + + return result; +} + +/** + * Applies all compatibility transformations to the endpoints based on the provided capabilities. + */ +export function applyCompatibilityTransformations( + endpoints: Endpoint[], + capabilities: ClientCapabilities, +): Endpoint[] { + let transformedEndpoints = [...endpoints]; + + // Handle top-level unions first as this changes tool names + if (!capabilities.topLevelUnions) { + const newEndpoints: Endpoint[] = []; + + for (const endpoint of transformedEndpoints) { + const variantTools = removeTopLevelUnions(endpoint.tool); + + if (variantTools.length === 1) { + newEndpoints.push(endpoint); + } else { + for (const variantTool of variantTools) { + newEndpoints.push({ + ...endpoint, + tool: variantTool, + }); + } + } + } + + transformedEndpoints = newEndpoints; + } + + if (capabilities.toolNameLength) { + const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); + const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); + + transformedEndpoints = transformedEndpoints.map((endpoint) => ({ + ...endpoint, + tool: { + ...endpoint.tool, + name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, + }, + })); + } + + if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { + transformedEndpoints = transformedEndpoints.map((endpoint) => { + let schema = endpoint.tool.inputSchema as JSONSchema; + + if (!capabilities.refs) { + schema = inlineRefs(schema); + } + + if (!capabilities.unions) { + schema = removeAnyOf(schema); + } + + if (!capabilities.formats) { + schema = removeFormats(schema, capabilities.formats); + } + + return { + ...endpoint, + tool: { + ...endpoint.tool, + inputSchema: schema as typeof endpoint.tool.inputSchema, + }, + }; + }); + } + + return transformedEndpoints; +} + +function toSnakeCase(str: string): string { + return str + .replace(/\s+/g, '_') + .replace(/([a-z])([A-Z])/g, '$1_$2') + .toLowerCase(); +} diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts new file mode 100644 index 00000000..47d60e0d --- /dev/null +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -0,0 +1,153 @@ +import ImageKit from '@imagekit/nodejs'; +import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { z } from 'zod'; +import { Cabidela } from '@cloudflare/cabidela'; + +function zodToInputSchema(schema: z.ZodSchema) { + return { + type: 'object' as const, + ...(zodToJsonSchema(schema) as any), + }; +} + +/** + * A list of tools that expose all the endpoints in the API dynamically. + * + * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then + * a generic endpoint that can be used to invoke any endpoint with the provided arguments. + * + * @param endpoints - The endpoints to include in the list. + */ +export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { + const listEndpointsSchema = z.object({ + search_query: z + .string() + .optional() + .describe( + 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', + ), + }); + + const listEndpointsTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'list_api_endpoints', + description: 'List or search for all endpoints in the Image Kit TypeScript API', + inputSchema: zodToInputSchema(listEndpointsSchema), + }, + handler: async (client: ImageKit, args: Record | undefined): Promise => { + const query = args && listEndpointsSchema.parse(args).search_query?.trim(); + + const filteredEndpoints = + query && query.length > 0 ? + endpoints.filter((endpoint) => { + const fieldsToMatch = [ + endpoint.tool.name, + endpoint.tool.description, + endpoint.metadata.resource, + endpoint.metadata.operation, + ...endpoint.metadata.tags, + ]; + return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); + }) + : endpoints; + + return asTextContentResult({ + tools: filteredEndpoints.map(({ tool, metadata }) => ({ + name: tool.name, + description: tool.description, + resource: metadata.resource, + operation: metadata.operation, + tags: metadata.tags, + })), + }); + }, + }; + + const getEndpointSchema = z.object({ + endpoint: z.string().describe('The name of the endpoint to get the schema for.'), + }); + const getEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'read' as const, + tags: [], + }, + tool: { + name: 'get_api_endpoint_schema', + description: + 'Get the schema for an endpoint in the Image Kit TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', + inputSchema: zodToInputSchema(getEndpointSchema), + }, + handler: async (client: ImageKit, args: Record | undefined) => { + if (!args) { + throw new Error('No endpoint provided'); + } + const endpointName = getEndpointSchema.parse(args).endpoint; + + const endpoint = endpoints.find((e) => e.tool.name === endpointName); + if (!endpoint) { + throw new Error(`Endpoint ${endpointName} not found`); + } + return asTextContentResult(endpoint.tool); + }, + }; + + const invokeEndpointSchema = z.object({ + endpoint_name: z.string().describe('The name of the endpoint to invoke.'), + args: z + .record(z.string(), z.any()) + .describe( + 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', + ), + }); + + const invokeEndpointTool = { + metadata: { + resource: 'dynamic_tools', + operation: 'write' as const, + tags: [], + }, + tool: { + name: 'invoke_api_endpoint', + description: + 'Invoke an endpoint in the Image Kit TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', + inputSchema: zodToInputSchema(invokeEndpointSchema), + }, + handler: async (client: ImageKit, args: Record | undefined): Promise => { + if (!args) { + throw new Error('No endpoint provided'); + } + const { success, data, error } = invokeEndpointSchema.safeParse(args); + if (!success) { + throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); + } + const { endpoint_name, args: endpointArgs } = data; + + const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); + if (!endpoint) { + throw new Error( + `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, + ); + } + + try { + // Try to validate the arguments for a better error message + const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); + cabidela.validate(endpointArgs); + } catch (error) { + throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); + } + + return await endpoint.handler(client, endpointArgs); + }, + }; + + return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; +} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts new file mode 100644 index 00000000..1aa9a40c --- /dev/null +++ b/packages/mcp-server/src/filtering.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +import initJq from 'jq-web'; + +export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { + if (jqFilter && typeof jqFilter === 'string') { + return await jq(response, jqFilter); + } else { + return response; + } +} + +async function jq(json: any, jqFilter: string) { + return (await initJq).json(json, jqFilter); +} diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts new file mode 100644 index 00000000..fdd56f48 --- /dev/null +++ b/packages/mcp-server/src/headers.ts @@ -0,0 +1,31 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { IncomingMessage } from 'node:http'; +import { ClientOptions } from '@imagekit/nodejs'; + +export const parseAuthHeaders = (req: IncomingMessage): Partial => { + if (req.headers.authorization) { + const scheme = req.headers.authorization.split(' ')[0]!; + const value = req.headers.authorization.slice(scheme.length + 1); + switch (scheme) { + case 'Basic': + const rawValue = Buffer.from(value, 'base64').toString(); + return { + privateKey: rawValue.slice(0, rawValue.search(':')), + password: rawValue.slice(rawValue.search(':') + 1), + }; + default: + throw new Error(`Unsupported authorization scheme`); + } + } + + const privateKey = + Array.isArray(req.headers['x-imagekit-private-api-key']) ? + req.headers['x-imagekit-private-api-key'][0] + : req.headers['x-imagekit-private-api-key']; + const password = + Array.isArray(req.headers['x-optional-imagekit-ignores-this']) ? + req.headers['x-optional-imagekit-ignores-this'][0] + : req.headers['x-optional-imagekit-ignores-this']; + return { privateKey, password }; +}; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts new file mode 100644 index 00000000..ec34ab47 --- /dev/null +++ b/packages/mcp-server/src/http.ts @@ -0,0 +1,127 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; + +import express from 'express'; +import { fromError } from 'zod-validation-error/v3'; +import { McpOptions, parseQueryOptions } from './options'; +import { ClientOptions, initMcpServer, newMcpServer } from './server'; +import { parseAuthHeaders } from './headers'; + +const newServer = ({ + clientOptions, + mcpOptions: defaultMcpOptions, + req, + res, +}: { + clientOptions: ClientOptions; + mcpOptions: McpOptions; + req: express.Request; + res: express.Response; +}): McpServer | null => { + const server = newMcpServer(); + + let mcpOptions: McpOptions; + try { + mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); + } catch (error) { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: `Invalid request: ${fromError(error)}`, + }, + }); + return null; + } + + try { + const authOptions = parseAuthHeaders(req); + initMcpServer({ + server: server, + clientOptions: { + ...clientOptions, + ...authOptions, + }, + mcpOptions, + }); + } catch { + res.status(401).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Unauthorized', + }, + }); + return null; + } + + return server; +}; + +const post = + (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) => + async (req: express.Request, res: express.Response) => { + const server = newServer({ ...options, req, res }); + // If we return null, we already set the authorization error. + if (server === null) return; + const transport = new StreamableHTTPServerTransport({ + // Stateless server + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + }; + +const get = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +const del = async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not supported', + }, + }); +}; + +export const streamableHTTPApp = ({ + clientOptions = {}, + mcpOptions = {}, +}: { + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}): express.Express => { + const app = express(); + app.set('query parser', 'extended'); + app.use(express.json()); + + app.get('/', get); + app.post('/', post({ clientOptions, mcpOptions })); + app.delete('/', del); + + return app; +}; + +export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { + const app = streamableHTTPApp({ mcpOptions: options }); + const server = app.listen(port); + const address = server.address(); + + if (typeof address === 'string') { + console.error(`MCP Server running on streamable HTTP at ${address}`); + } else if (address !== null) { + console.error(`MCP Server running on streamable HTTP on port ${address.port}`); + } else { + console.error(`MCP Server running on streamable HTTP on port ${port}`); + } +}; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts new file mode 100644 index 00000000..4850a0e2 --- /dev/null +++ b/packages/mcp-server/src/index.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +import { selectTools } from './server'; +import { Endpoint, endpoints } from './tools'; +import { McpOptions, parseCLIOptions } from './options'; +import { launchStdioServer } from './stdio'; +import { launchStreamableHTTPServer } from './http'; + +async function main() { + const options = parseOptionsOrError(); + + if (options.list) { + listAllTools(); + return; + } + + const selectedTools = await selectToolsOrError(endpoints, options); + + console.error( + `MCP Server starting with ${selectedTools.length} tools:`, + selectedTools.map((e) => e.tool.name), + ); + + switch (options.transport) { + case 'stdio': + await launchStdioServer(options); + break; + case 'http': + await launchStreamableHTTPServer(options, options.port ?? options.socket); + break; + } +} + +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error in main():', error); + process.exit(1); + }); +} + +function parseOptionsOrError() { + try { + return parseCLIOptions(); + } catch (error) { + console.error('Error parsing options:', error); + process.exit(1); + } +} + +async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { + try { + const includedTools = await selectTools(endpoints, options); + if (includedTools.length === 0) { + console.error('No tools match the provided filters.'); + process.exit(1); + } + return includedTools; + } catch (error) { + if (error instanceof Error) { + console.error('Error filtering tools:', error.message); + } else { + console.error('Error filtering tools:', error); + } + process.exit(1); + } +} + +function listAllTools() { + if (endpoints.length === 0) { + console.log('No tools available.'); + return; + } + console.log('Available tools:\n'); + + // Group endpoints by resource + const resourceGroups = new Map(); + + for (const endpoint of endpoints) { + const resource = endpoint.metadata.resource; + if (!resourceGroups.has(resource)) { + resourceGroups.set(resource, []); + } + resourceGroups.get(resource)!.push(endpoint); + } + + // Sort resources alphabetically + const sortedResources = Array.from(resourceGroups.keys()).sort(); + + // Display hierarchically by resource + for (const resource of sortedResources) { + console.log(`Resource: ${resource}`); + + const resourceEndpoints = resourceGroups.get(resource)!; + // Sort endpoints by tool name + resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); + + for (const endpoint of resourceEndpoints) { + const { + tool, + metadata: { operation, tags }, + } = endpoint; + + console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); + console.log(` Description: ${tool.description}`); + } + console.log(''); + } +} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts new file mode 100644 index 00000000..ecc9f10e --- /dev/null +++ b/packages/mcp-server/src/options.ts @@ -0,0 +1,456 @@ +import qs from 'qs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import z from 'zod'; +import { endpoints, Filter } from './tools'; +import { ClientCapabilities, knownClients, ClientType } from './compat'; + +export type CLIOptions = McpOptions & { + list: boolean; + transport: 'stdio' | 'http'; + port: number | undefined; + socket: string | undefined; +}; + +export type McpOptions = { + client?: ClientType | undefined; + includeDynamicTools?: boolean | undefined; + includeAllTools?: boolean | undefined; + includeCodeTools?: boolean | undefined; + filters?: Filter[] | undefined; + capabilities?: Partial | undefined; +}; + +const CAPABILITY_CHOICES = [ + 'top-level-unions', + 'valid-json', + 'refs', + 'unions', + 'formats', + 'tool-name-length', +] as const; + +type Capability = (typeof CAPABILITY_CHOICES)[number]; + +function parseCapabilityValue(cap: string): { name: Capability; value?: number } { + if (cap.startsWith('tool-name-length=')) { + const parts = cap.split('='); + if (parts.length === 2) { + const length = parseInt(parts[1]!, 10); + if (!isNaN(length)) { + return { name: 'tool-name-length', value: length }; + } + throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); + } + throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); + } + if (!CAPABILITY_CHOICES.includes(cap as Capability)) { + throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); + } + return { name: cap as Capability }; +} + +export function parseCLIOptions(): CLIOptions { + const opts = yargs(hideBin(process.argv)) + .option('tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code'], + description: 'Use dynamic tools or all tools', + }) + .option('no-tools', { + type: 'string', + array: true, + choices: ['dynamic', 'all', 'code'], + description: 'Do not use any dynamic or all tools', + }) + .option('tool', { + type: 'string', + array: true, + description: 'Include tools matching the specified names', + }) + .option('resource', { + type: 'string', + array: true, + description: 'Include tools matching the specified resources', + }) + .option('operation', { + type: 'string', + array: true, + choices: ['read', 'write'], + description: 'Include tools matching the specified operations', + }) + .option('tag', { + type: 'string', + array: true, + description: 'Include tools with the specified tags', + }) + .option('no-tool', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified names', + }) + .option('no-resource', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified resources', + }) + .option('no-operation', { + type: 'string', + array: true, + description: 'Exclude tools matching the specified operations', + }) + .option('no-tag', { + type: 'string', + array: true, + description: 'Exclude tools with the specified tags', + }) + .option('list', { + type: 'boolean', + description: 'List all tools and exit', + }) + .option('client', { + type: 'string', + choices: Object.keys(knownClients), + description: 'Specify the MCP client being used', + }) + .option('capability', { + type: 'string', + array: true, + description: 'Specify client capabilities', + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('no-capability', { + type: 'string', + array: true, + description: 'Unset client capabilities', + choices: CAPABILITY_CHOICES, + coerce: (values: string[]) => { + return values.flatMap((v) => v.split(',')); + }, + }) + .option('describe-capabilities', { + type: 'boolean', + description: 'Print detailed explanation of client capabilities and exit', + }) + .option('transport', { + type: 'string', + choices: ['stdio', 'http'], + default: 'stdio', + description: 'What transport to use; stdio for local servers or http for remote servers', + }) + .option('port', { + type: 'number', + description: 'Port to serve on if using http transport', + }) + .option('socket', { + type: 'string', + description: 'Unix socket to serve on if using http transport', + }) + .help(); + + for (const [command, desc] of examples()) { + opts.example(command, desc); + } + + const argv = opts.parseSync(); + + // Handle describe-capabilities flag + if (argv.describeCapabilities) { + console.log(getCapabilitiesExplanation()); + process.exit(0); + } + + const filters: Filter[] = []; + + // Helper function to support comma-separated values + const splitValues = (values: string[] | undefined): string[] => { + if (!values) return []; + return values.flatMap((v) => v.split(',')); + }; + + for (const tag of splitValues(argv.tag)) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + + for (const tag of splitValues(argv.noTag)) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + + for (const resource of splitValues(argv.resource)) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + + for (const resource of splitValues(argv.noResource)) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + + for (const tool of splitValues(argv.tool)) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + + for (const tool of splitValues(argv.noTool)) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + for (const operation of splitValues(argv.operation)) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + + for (const operation of splitValues(argv.noOperation)) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + + // Parse client capabilities + const clientCapabilities: Partial = {}; + + // Apply individual capability overrides + if (Array.isArray(argv.capability)) { + for (const cap of argv.capability) { + const parsedCap = parseCapabilityValue(cap); + if (parsedCap.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsedCap.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsedCap.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsedCap.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsedCap.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsedCap.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsedCap.value; + } + } + } + + // Handle no-capability options to unset capabilities + if (Array.isArray(argv.noCapability)) { + for (const cap of argv.noCapability) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + } + + const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code') => + explicitTools ? argv.tools?.includes(toolType) && !argv.noTools?.includes(toolType) : undefined; + + const explicitTools = Boolean(argv.tools || argv.noTools); + const includeDynamicTools = shouldIncludeToolType('dynamic'); + const includeAllTools = shouldIncludeToolType('all'); + const includeCodeTools = shouldIncludeToolType('code'); + + const transport = argv.transport as 'stdio' | 'http'; + + const client = argv.client as ClientType; + return { + client: client && client !== 'infer' && knownClients[client] ? client : undefined, + includeDynamicTools, + includeAllTools, + includeCodeTools, + filters, + capabilities: clientCapabilities, + list: argv.list || false, + transport, + port: argv.port, + socket: argv.socket, + }; +} + +const coerceArray = (zodType: T) => + z.preprocess( + (val) => + Array.isArray(val) ? val + : val ? [val] + : val, + z.array(zodType).optional(), + ); + +const QueryOptions = z.object({ + tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Use dynamic tools or all tools'), + no_tools: coerceArray(z.enum(['dynamic', 'all'])).describe('Do not use dynamic tools or all tools'), + tool: coerceArray(z.string()).describe('Include tools matching the specified names'), + resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), + operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Include tools matching the specified operations', + ), + tag: coerceArray(z.string()).describe('Include tools with the specified tags'), + no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), + no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), + no_operation: coerceArray(z.enum(['read', 'write'])).describe( + 'Exclude tools matching the specified operations', + ), + no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), + client: ClientType.optional().describe('Specify the MCP client being used'), + capability: coerceArray(z.string()).describe('Specify client capabilities'), + no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), +}); + +export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { + const queryObject = typeof query === 'string' ? qs.parse(query) : query; + const queryOptions = QueryOptions.parse(queryObject); + + const filters: Filter[] = [...(defaultOptions.filters ?? [])]; + + for (const resource of queryOptions.resource || []) { + filters.push({ type: 'resource', op: 'include', value: resource }); + } + for (const operation of queryOptions.operation || []) { + filters.push({ type: 'operation', op: 'include', value: operation }); + } + for (const tag of queryOptions.tag || []) { + filters.push({ type: 'tag', op: 'include', value: tag }); + } + for (const tool of queryOptions.tool || []) { + filters.push({ type: 'tool', op: 'include', value: tool }); + } + for (const resource of queryOptions.no_resource || []) { + filters.push({ type: 'resource', op: 'exclude', value: resource }); + } + for (const operation of queryOptions.no_operation || []) { + filters.push({ type: 'operation', op: 'exclude', value: operation }); + } + for (const tag of queryOptions.no_tag || []) { + filters.push({ type: 'tag', op: 'exclude', value: tag }); + } + for (const tool of queryOptions.no_tool || []) { + filters.push({ type: 'tool', op: 'exclude', value: tool }); + } + + // Parse client capabilities + const clientCapabilities: Partial = { ...defaultOptions.capabilities }; + + for (const cap of queryOptions.capability || []) { + const parsed = parseCapabilityValue(cap); + if (parsed.name === 'top-level-unions') { + clientCapabilities.topLevelUnions = true; + } else if (parsed.name === 'valid-json') { + clientCapabilities.validJson = true; + } else if (parsed.name === 'refs') { + clientCapabilities.refs = true; + } else if (parsed.name === 'unions') { + clientCapabilities.unions = true; + } else if (parsed.name === 'formats') { + clientCapabilities.formats = true; + } else if (parsed.name === 'tool-name-length') { + clientCapabilities.toolNameLength = parsed.value; + } + } + + for (const cap of queryOptions.no_capability || []) { + if (cap === 'top-level-unions') { + clientCapabilities.topLevelUnions = false; + } else if (cap === 'valid-json') { + clientCapabilities.validJson = false; + } else if (cap === 'refs') { + clientCapabilities.refs = false; + } else if (cap === 'unions') { + clientCapabilities.unions = false; + } else if (cap === 'formats') { + clientCapabilities.formats = false; + } else if (cap === 'tool-name-length') { + clientCapabilities.toolNameLength = undefined; + } + } + + let dynamicTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false + : queryOptions.tools?.includes('dynamic') ? true + : defaultOptions.includeDynamicTools; + + let allTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false + : queryOptions.tools?.includes('all') ? true + : defaultOptions.includeAllTools; + + return { + client: queryOptions.client ?? defaultOptions.client, + includeDynamicTools: dynamicTools, + includeAllTools: allTools, + includeCodeTools: undefined, + filters, + capabilities: clientCapabilities, + }; +} + +function getCapabilitiesExplanation(): string { + return ` +Client Capabilities Explanation: + +Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. + +When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. + +Available Capabilities: + +# top-level-unions +Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. + +# refs +Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. + +# valid-json +Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. + +# unions +Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. + +# formats +Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. + +# tool-name-length=N +Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. + +Client Presets (--client): +Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. + +Current presets: +${JSON.stringify(knownClients, null, 2)} + `; +} + +function examples(): [string, string][] { + const firstEndpoint = endpoints[0]!; + const secondEndpoint = + endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; + const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; + const otherEndpoint = secondEndpoint || firstEndpoint; + + return [ + [ + `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, + 'Include tools by name', + ], + [ + `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, + 'Filter by resource and operation', + ], + [ + `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, + 'Use resource wildcards and exclusions', + ], + [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], + [ + `--capability="top-level-unions" --capability="tool-name-length=40"`, + 'Specify individual client capabilities', + ], + [ + `--client="cursor" --no-capability="tool-name-length"`, + 'Use cursor client preset but remove tool name length limit', + ], + ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), + ]; +} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts new file mode 100644 index 00000000..d9bcbcb2 --- /dev/null +++ b/packages/mcp-server/src/server.ts @@ -0,0 +1,204 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { Endpoint, endpoints, HandlerFunction, query } from './tools'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + SetLevelRequestSchema, + Implementation, + Tool, +} from '@modelcontextprotocol/sdk/types.js'; +import { ClientOptions } from '@imagekit/nodejs'; +import ImageKit from '@imagekit/nodejs'; +import { + applyCompatibilityTransformations, + ClientCapabilities, + defaultClientCapabilities, + knownClients, + parseEmbeddedJSON, +} from './compat'; +import { dynamicTools } from './dynamic-tools'; +import { codeTool } from './code-tool'; +import { McpOptions } from './options'; + +export { McpOptions } from './options'; +export { ClientType } from './compat'; +export { Filter } from './tools'; +export { ClientOptions } from '@imagekit/nodejs'; +export { endpoints } from './tools'; + +export const newMcpServer = () => + new McpServer( + { + name: 'imagekit_nodejs_api', + version: '0.0.1-alpha.0', + }, + { capabilities: { tools: {}, logging: {} } }, + ); + +// Create server instance +export const server = newMcpServer(); + +/** + * Initializes the provided MCP Server with the given tools and handlers. + * If not provided, the default client, tools and handlers will be used. + */ +export function initMcpServer(params: { + server: Server | McpServer; + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}) { + const server = params.server instanceof McpServer ? params.server.server : params.server; + const mcpOptions = params.mcpOptions ?? {}; + + let providedEndpoints: Endpoint[] | null = null; + let endpointMap: Record | null = null; + + const initTools = async (implementation?: Implementation) => { + if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { + mcpOptions.client = + implementation.name.toLowerCase().includes('claude') ? 'claude' + : implementation.name.toLowerCase().includes('cursor') ? 'cursor' + : undefined; + mcpOptions.capabilities = { + ...(mcpOptions.client && knownClients[mcpOptions.client]), + ...mcpOptions.capabilities, + }; + } + providedEndpoints ??= await selectTools(endpoints, mcpOptions); + endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); + }; + + const logAtLevel = + (level: 'debug' | 'info' | 'warning' | 'error') => + (message: string, ...rest: unknown[]) => { + void server.sendLoggingMessage({ + level, + data: { message, rest }, + }); + }; + const logger = { + debug: logAtLevel('debug'), + info: logAtLevel('info'), + warn: logAtLevel('warning'), + error: logAtLevel('error'), + }; + + let client = new ImageKit({ + logger, + ...params.clientOptions, + defaultHeaders: { + ...params.clientOptions?.defaultHeaders, + 'X-Stainless-MCP': 'true', + }, + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + if (providedEndpoints === null) { + await initTools(server.getClientVersion()); + } + return { + tools: providedEndpoints!.map((endpoint) => endpoint.tool), + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (endpointMap === null) { + await initTools(server.getClientVersion()); + } + const { name, arguments: args } = request.params; + const endpoint = endpointMap![name]; + if (!endpoint) { + throw new Error(`Unknown tool: ${name}`); + } + + return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + switch (level) { + case 'debug': + client = client.withOptions({ logLevel: 'debug' }); + break; + case 'info': + client = client.withOptions({ logLevel: 'info' }); + break; + case 'notice': + case 'warning': + client = client.withOptions({ logLevel: 'warn' }); + break; + case 'error': + client = client.withOptions({ logLevel: 'error' }); + break; + default: + client = client.withOptions({ logLevel: 'off' }); + break; + } + return {}; + }); +} + +/** + * Selects the tools to include in the MCP Server based on the provided options. + */ +export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { + const filteredEndpoints = query(options?.filters ?? [], endpoints); + + let includedTools = filteredEndpoints; + + if (includedTools.length > 0) { + if (options?.includeDynamicTools) { + includedTools = dynamicTools(includedTools); + } + } else { + if (options?.includeAllTools) { + includedTools = endpoints; + } else if (options?.includeDynamicTools) { + includedTools = dynamicTools(endpoints); + } else if (options?.includeCodeTools) { + includedTools = [await codeTool()]; + } else { + includedTools = endpoints; + } + } + + const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; + return applyCompatibilityTransformations(includedTools, capabilities); +} + +/** + * Runs the provided handler with the given client and arguments. + */ +export async function executeHandler( + tool: Tool, + handler: HandlerFunction, + client: ImageKit, + args: Record | undefined, + compatibilityOptions?: Partial, +) { + const options = { ...defaultClientCapabilities, ...compatibilityOptions }; + if (!options.validJson && args) { + args = parseEmbeddedJSON(args, tool.inputSchema); + } + return await handler(client, args || {}); +} + +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim(); + } else if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim(); + } + return; +}; + +export const readEnvOrError = (env: string): string => { + let envValue = readEnv(env); + if (envValue === undefined) { + throw new Error(`Environment variable ${env} is not set`); + } + return envValue; +}; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts new file mode 100644 index 00000000..d902a5bb --- /dev/null +++ b/packages/mcp-server/src/stdio.ts @@ -0,0 +1,13 @@ +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { initMcpServer, newMcpServer } from './server'; +import { McpOptions } from './options'; + +export const launchStdioServer = async (options: McpOptions) => { + const server = newMcpServer(); + + initMcpServer({ server, mcpOptions: options }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP Server running on stdio'); +}; diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts new file mode 100644 index 00000000..7e516de7 --- /dev/null +++ b/packages/mcp-server/src/tools.ts @@ -0,0 +1 @@ +export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts new file mode 100644 index 00000000..e1775bd4 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/create-accounts-origins.ts @@ -0,0 +1,318 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/accounts/origins', + operationId: 'create-origin', +}; + +export const tool: Tool = { + name: 'create_accounts_origins', + description: + '**Note:** This API is currently in beta. \nCreates a new origin and returns the origin object.\n', + inputSchema: { + type: 'object', + anyOf: [ + { + type: 'object', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + endpoint: { + type: 'string', + description: 'Custom S3-compatible endpoint.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3_COMPATIBLE'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + s3ForcePathStyle: { + type: 'boolean', + description: 'Use path-style S3 URLs?', + }, + }, + required: ['accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['CLOUDINARY_BACKUP'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + baseUrl: { + type: 'string', + description: 'Root URL for the web folder origin.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_FOLDER'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + forwardHostHeaderToOrigin: { + type: 'boolean', + description: 'Forward the Host header to origin?', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'name', 'type'], + }, + { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_PROXY'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['name', 'type'], + }, + { + type: 'object', + properties: { + bucket: { + type: 'string', + }, + clientEmail: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + privateKey: { + type: 'string', + }, + type: { + type: 'string', + enum: ['GCS'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['bucket', 'clientEmail', 'name', 'privateKey', 'type'], + }, + { + type: 'object', + properties: { + accountName: { + type: 'string', + }, + container: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + sasToken: { + type: 'string', + }, + type: { + type: 'string', + enum: ['AZURE_BLOB'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['accountName', 'container', 'name', 'sasToken', 'type'], + }, + { + type: 'object', + properties: { + baseUrl: { + type: 'string', + description: 'Akeneo instance base URL.', + }, + clientId: { + type: 'string', + description: 'Akeneo API client ID.', + }, + clientSecret: { + type: 'string', + description: 'Akeneo API client secret.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + password: { + type: 'string', + description: 'Akeneo API password.', + }, + type: { + type: 'string', + enum: ['AKENEO_PIM'], + }, + username: { + type: 'string', + description: 'Akeneo API username.', + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], + }, + ], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.accounts.origins.create(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts new file mode 100644 index 00000000..95c2ec3c --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/delete-accounts-origins.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'delete-origin', +}; + +export const tool: Tool = { + name: 'delete_accounts_origins', + description: + '**Note:** This API is currently in beta. \nPermanently removes the origin identified by `id`. If the origin is in use by any URL‑endpoints, the API will return an error.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + const response = await client.accounts.origins.delete(id).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts new file mode 100644 index 00000000..45a2d9b7 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/get-accounts-origins.ts @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'get-origin', +}; + +export const tool: Tool = { + name: 'get_accounts_origins', + description: '**Note:** This API is currently in beta. \nRetrieves the origin identified by `id`.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.accounts.origins.get(id)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts new file mode 100644 index 00000000..ed36d53b --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/list-accounts-origins.ts @@ -0,0 +1,35 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/origins', + operationId: 'list-origins', +}; + +export const tool: Tool = { + name: 'list_accounts_origins', + description: + '**Note:** This API is currently in beta. \nReturns an array of all configured origins for the current account.\n', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + return asTextContentResult(await client.accounts.origins.list()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts new file mode 100644 index 00000000..3cc2da7b --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/origins/update-accounts-origins.ts @@ -0,0 +1,360 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.origins', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/accounts/origins/{id}', + operationId: 'update-origin', +}; + +export const tool: Tool = { + name: 'update_accounts_origins', + description: + '**Note:** This API is currently in beta. \nUpdates the origin identified by `id` and returns the updated origin object.\n', + inputSchema: { + type: 'object', + anyOf: [ + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['id', 'accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + endpoint: { + type: 'string', + description: 'Custom S3-compatible endpoint.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['S3_COMPATIBLE'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + s3ForcePathStyle: { + type: 'boolean', + description: 'Use path-style S3 URLs?', + }, + }, + required: ['id', 'accessKey', 'bucket', 'endpoint', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + accessKey: { + type: 'string', + description: 'Access key for the bucket.', + }, + bucket: { + type: 'string', + description: 'S3 bucket name.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + secretKey: { + type: 'string', + description: 'Secret key for the bucket.', + }, + type: { + type: 'string', + enum: ['CLOUDINARY_BACKUP'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + description: 'Path prefix inside the bucket.', + }, + }, + required: ['id', 'accessKey', 'bucket', 'name', 'secretKey', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + baseUrl: { + type: 'string', + description: 'Root URL for the web folder origin.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_FOLDER'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + forwardHostHeaderToOrigin: { + type: 'boolean', + description: 'Forward the Host header to origin?', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['id', 'baseUrl', 'name', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + type: { + type: 'string', + enum: ['WEB_PROXY'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['id', 'name', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + bucket: { + type: 'string', + }, + clientEmail: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + privateKey: { + type: 'string', + }, + type: { + type: 'string', + enum: ['GCS'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['id', 'bucket', 'clientEmail', 'name', 'privateKey', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + accountName: { + type: 'string', + }, + container: { + type: 'string', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + sasToken: { + type: 'string', + }, + type: { + type: 'string', + enum: ['AZURE_BLOB'], + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + prefix: { + type: 'string', + }, + }, + required: ['id', 'accountName', 'container', 'name', 'sasToken', 'type'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + baseUrl: { + type: 'string', + description: 'Akeneo instance base URL.', + }, + clientId: { + type: 'string', + description: 'Akeneo API client ID.', + }, + clientSecret: { + type: 'string', + description: 'Akeneo API client secret.', + }, + name: { + type: 'string', + description: 'Display name of the origin.', + }, + password: { + type: 'string', + description: 'Akeneo API password.', + }, + type: { + type: 'string', + enum: ['AKENEO_PIM'], + }, + username: { + type: 'string', + description: 'Akeneo API username.', + }, + baseUrlForCanonicalHeader: { + type: 'string', + description: 'URL used in the Canonical header (if enabled).', + }, + includeCanonicalHeader: { + type: 'boolean', + description: 'Whether to send a Canonical header.', + }, + }, + required: ['id', 'baseUrl', 'clientId', 'clientSecret', 'name', 'password', 'type', 'username'], + }, + ], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + return asTextContentResult(await client.accounts.origins.update(id, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts new file mode 100644 index 00000000..b7457fcf --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/create-accounts-url-endpoints.ts @@ -0,0 +1,103 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/accounts/url-endpoints', + operationId: 'create-url-endpoint', +}; + +export const tool: Tool = { + name: 'create_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nCreates a new URL‑endpoint and returns the resulting object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + description: { + type: 'string', + description: 'Description of the URL endpoint.', + }, + origins: { + type: 'array', + description: + 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', + items: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + urlPrefix: { + type: 'string', + description: + 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', + }, + urlRewriter: { + anyOf: [ + { + type: 'object', + title: 'Cloudinary URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['CLOUDINARY'], + }, + preserveAssetDeliveryTypes: { + type: 'boolean', + description: 'Whether to preserve `/` in the rewritten URL.', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Imgix URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['IMGIX'], + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Akamai URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['AKAMAI'], + }, + }, + required: ['type'], + }, + ], + description: 'Configuration for third-party URL rewriting.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['description'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts new file mode 100644 index 00000000..da7f6134 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/delete-accounts-url-endpoints.ts @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'delete-url-endpoint', +}; + +export const tool: Tool = { + name: 'delete_accounts_url_endpoints', + description: + '**Note:** This API is currently in beta. \nDeletes the URL‑endpoint identified by `id`. You cannot delete the default URL‑endpoint created by ImageKit during account creation.\n', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, ...body } = args as any; + const response = await client.accounts.urlEndpoints.delete(id).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts new file mode 100644 index 00000000..f21f9c8b --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/get-accounts-url-endpoints.ts @@ -0,0 +1,49 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'get-url-endpoint', +}; + +export const tool: Tool = { + name: 'get_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nRetrieves the URL‑endpoint identified by `id`.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.get(id))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts new file mode 100644 index 00000000..f33b762b --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/list-accounts-url-endpoints.ts @@ -0,0 +1,44 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/url-endpoints', + operationId: 'list-url-endpoints', +}; + +export const tool: Tool = { + name: 'list_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nReturns an array of all URL‑endpoints configured including the default URL-endpoint generated by ImageKit during account creation.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/url_endpoint_response'\n },\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.urlEndpoints.list())); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts new file mode 100644 index 00000000..0ff252f4 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/url-endpoints/update-accounts-url-endpoints.ts @@ -0,0 +1,112 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.urlEndpoints', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/accounts/url-endpoints/{id}', + operationId: 'update-url-endpoint', +}; + +export const tool: Tool = { + name: 'update_accounts_url_endpoints', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n**Note:** This API is currently in beta. \nUpdates the URL‑endpoint identified by `id` and returns the updated object.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/url_endpoint_response',\n $defs: {\n url_endpoint_response: {\n type: 'object',\n title: 'URL‑endpoint Response',\n description: 'URL‑endpoint object as returned by the API.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.'\n },\n description: {\n type: 'string',\n description: 'Description of the URL endpoint.'\n },\n origins: {\n type: 'array',\n description: 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.',\n items: {\n type: 'string',\n description: 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.'\n }\n },\n urlPrefix: {\n type: 'string',\n description: 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).'\n },\n urlRewriter: {\n anyOf: [ {\n type: 'object',\n title: 'Cloudinary URL Rewriter',\n properties: {\n preserveAssetDeliveryTypes: {\n type: 'boolean',\n description: 'Whether to preserve `/` in the rewritten URL.'\n },\n type: {\n type: 'string',\n enum: [ 'CLOUDINARY'\n ]\n }\n },\n required: [ 'preserveAssetDeliveryTypes',\n 'type'\n ]\n },\n {\n type: 'object',\n title: 'Imgix URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'IMGIX'\n ]\n }\n },\n required: [ 'type'\n ]\n },\n {\n type: 'object',\n title: 'Akamai URL Rewriter',\n properties: {\n type: {\n type: 'string',\n enum: [ 'AKAMAI'\n ]\n }\n },\n required: [ 'type'\n ]\n }\n ],\n description: 'Configuration for third-party URL rewriting.'\n }\n },\n required: [ 'id',\n 'description',\n 'origins',\n 'urlPrefix'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Unique identifier for the URL-endpoint. This is generated by ImageKit when you create a new URL-endpoint. For the default URL-endpoint, this is always `default`.', + }, + description: { + type: 'string', + description: 'Description of the URL endpoint.', + }, + origins: { + type: 'array', + description: + 'Ordered list of origin IDs to try when the file isn’t in the Media Library; ImageKit checks them in the sequence provided. Origin must be created before it can be used in a URL endpoint.', + items: { + type: 'string', + description: + 'Unique identifier for the origin. This is generated by ImageKit when you create a new origin.', + }, + }, + urlPrefix: { + type: 'string', + description: + 'Path segment appended to your base URL to form the endpoint (letters, digits, and hyphens only — or empty for the default endpoint).', + }, + urlRewriter: { + anyOf: [ + { + type: 'object', + title: 'Cloudinary URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['CLOUDINARY'], + }, + preserveAssetDeliveryTypes: { + type: 'boolean', + description: 'Whether to preserve `/` in the rewritten URL.', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Imgix URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['IMGIX'], + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Akamai URL Rewriter', + properties: { + type: { + type: 'string', + enum: ['AKAMAI'], + }, + }, + required: ['type'], + }, + ], + description: 'Configuration for third-party URL rewriting.', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id', 'description'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.accounts.urlEndpoints.update(id, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts new file mode 100644 index 00000000..5504a690 --- /dev/null +++ b/packages/mcp-server/src/tools/accounts/usage/get-accounts-usage.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'accounts.usage', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/accounts/usage', + operationId: 'get-usage', +}; + +export const tool: Tool = { + name: 'get_accounts_usage', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet the account usage information between two dates. Note that the API response includes data from the start date while excluding data from the end date. In other words, the data covers the period starting from the specified start date up to, but not including, the end date.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n bandwidthBytes: {\n type: 'integer',\n description: 'Amount of bandwidth used in bytes.'\n },\n extensionUnitsCount: {\n type: 'integer',\n description: 'Number of extension units used.'\n },\n mediaLibraryStorageBytes: {\n type: 'integer',\n description: 'Storage used by media library in bytes.'\n },\n originalCacheStorageBytes: {\n type: 'integer',\n description: 'Storage used by the original cache in bytes.'\n },\n videoProcessingUnitsCount: {\n type: 'integer',\n description: 'Number of video processing units used.'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + endDate: { + type: 'string', + description: + 'Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days.', + format: 'date', + }, + startDate: { + type: 'string', + description: + 'Specify a `startDate` in `YYYY-MM-DD` format. It should be before the `endDate`. The difference between `startDate` and `endDate` should be less than 90 days.', + format: 'date', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['endDate', 'startDate'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.accounts.usage.get(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/assets/list-assets.ts b/packages/mcp-server/src/tools/assets/list-assets.ts new file mode 100644 index 00000000..7ed71596 --- /dev/null +++ b/packages/mcp-server/src/tools/assets/list-assets.ts @@ -0,0 +1,94 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'assets', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files', + operationId: 'list-and-search-assets', +}; + +export const tool: Tool = { + name: 'list_assets', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API can list all the uploaded files and folders in your ImageKit.io media library. In addition, you can fine-tune your query by specifying various filters by generating a query string in a Lucene-like syntax and provide this generated string as the value of the `searchQuery`.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n anyOf: [ {\n $ref: '#/$defs/file'\n },\n {\n $ref: '#/$defs/folder'\n }\n ],\n description: 'Object containing details of a file or file version.'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n },\n folder: {\n type: 'object',\n title: 'Folder',\n properties: {\n createdAt: {\n type: 'string',\n description: 'Date and time when the folder was created. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n folderId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n folderPath: {\n type: 'string',\n description: 'Path of the folder. This is the path you would use in the URL to access the folder. For example, if the folder is at the root of the media library, the path will be /folder. If the folder is inside another folder named images, the path will be /images/folder.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'folder'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the folder was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileType: { + type: 'string', + description: + 'Filter results by file type.\n\n- `all` — include all file types \n- `image` — include only image files \n- `non-image` — include only non-image files (e.g., JS, CSS, video)', + enum: ['all', 'image', 'non-image'], + }, + limit: { + type: 'integer', + description: 'The maximum number of results to return in response.\n', + }, + path: { + type: 'string', + description: + 'Folder path if you want to limit the search within a specific folder. For example, `/sales-banner/` will only search in folder sales-banner.\n\nNote : If your use case involves searching within a folder as well as its subfolders, you can use `path` parameter in `searchQuery` with appropriate operator.\nCheckout [Supported parameters](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#supported-parameters) for more information.\n', + }, + searchQuery: { + type: 'string', + description: + 'Query string in a Lucene-like query language e.g. `createdAt > "7d"`.\n\nNote : When the searchQuery parameter is present, the following query parameters will have no effect on the result:\n\n1. `tags`\n2. `type`\n3. `name`\n\n[Learn more](/docs/api-reference/digital-asset-management-dam/list-and-search-assets#advanced-search-queries) from examples.\n', + }, + skip: { + type: 'integer', + description: 'The number of results to skip before returning results.\n', + }, + sort: { + type: 'string', + description: 'Sort the results by one of the supported fields in ascending or descending order.', + enum: [ + 'ASC_NAME', + 'DESC_NAME', + 'ASC_CREATED', + 'DESC_CREATED', + 'ASC_UPDATED', + 'DESC_UPDATED', + 'ASC_HEIGHT', + 'DESC_HEIGHT', + 'ASC_WIDTH', + 'DESC_WIDTH', + 'ASC_SIZE', + 'DESC_SIZE', + 'ASC_RELEVANCE', + 'DESC_RELEVANCE', + ], + }, + type: { + type: 'string', + description: + 'Filter results by asset type.\n\n- `file` — returns only files \n- `file-version` — returns specific file versions \n- `folder` — returns only folders \n- `all` — returns both files and folders (excludes `file-version`)', + enum: ['file', 'file-version', 'folder', 'all'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.assets.list(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts new file mode 100644 index 00000000..fcb48cae --- /dev/null +++ b/packages/mcp-server/src/tools/beta/v2/files/upload-v2-beta-files.ts @@ -0,0 +1,315 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'beta.v2.files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/api/v2/files/upload', + operationId: 'upload-file-v2', +}; + +export const tool: Tool = { + name: 'upload_v2_beta_files', + description: + 'The V2 API enhances security by verifying the entire payload using JWT. This API is in beta.\n\nImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file-v2#how-to-implement-secure-client-side-file-upload) about how to implement secure client-side file upload.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files, and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files, and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', + inputSchema: { + type: 'object', + properties: { + file: { + type: 'string', + description: + 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', + }, + fileName: { + type: 'string', + description: 'The name with which the file has to be uploaded.\n', + }, + token: { + type: 'string', + description: + "This is the client-generated JSON Web Token (JWT). The ImageKit.io server uses it to authenticate and check that the upload request parameters have not been tampered with after the token has been generated. Learn how to create the token on the page below. This field is only required for authentication when uploading a file from the client side.\n\n\n**Note**: Sending a JWT that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new token.\n\n\n**⚠️Warning**: JWT must be generated on the server-side because it is generated using your account's private API key. This field is required for authentication when uploading a file from the client-side.\n", + }, + checks: { + type: 'string', + description: + 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file-v2#upload-api-checks).\n', + }, + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', + }, + customMetadata: { + type: 'object', + description: + 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + extensions: { + $ref: '#/$defs/extensions', + }, + folder: { + type: 'string', + description: + "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. Using multiple `/` creates a nested folder.\n", + }, + isPrivateFile: { + type: 'boolean', + description: + 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', + }, + isPublished: { + type: 'boolean', + description: + 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', + }, + overwriteAITags: { + type: 'boolean', + description: + 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', + }, + overwriteCustomMetadata: { + type: 'boolean', + description: + 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', + }, + overwriteFile: { + type: 'boolean', + description: + 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', + }, + overwriteTags: { + type: 'boolean', + description: + 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', + }, + responseFields: { + type: 'array', + description: 'Array of response field keys to include in the API response body.\n', + items: { + type: 'string', + enum: [ + 'tags', + 'customCoordinates', + 'isPrivateFile', + 'embeddedMetadata', + 'isPublished', + 'customMetadata', + 'metadata', + ], + }, + }, + tags: { + type: 'array', + description: + 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', + items: { + type: 'string', + }, + }, + transformation: { + type: 'object', + description: + "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", + properties: { + post: { + type: 'array', + description: + 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Simple post-transformation', + properties: { + type: { + type: 'string', + description: 'Transformation type.', + enum: ['transformation'], + }, + value: { + type: 'string', + description: + 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', + }, + }, + required: ['type', 'value'], + }, + { + type: 'object', + title: 'Convert GIF to video', + properties: { + type: { + type: 'string', + description: 'Converts an animated GIF into an MP4.', + enum: ['gif-to-video'], + }, + value: { + type: 'string', + description: + 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Generate a thumbnail', + properties: { + type: { + type: 'string', + description: 'Generates a thumbnail image.', + enum: ['thumbnail'], + }, + value: { + type: 'string', + description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Adaptive Bitrate Streaming', + properties: { + protocol: { + type: 'string', + description: 'Streaming protocol to use (`hls` or `dash`).', + enum: ['hls', 'dash'], + }, + type: { + type: 'string', + description: 'Adaptive Bitrate Streaming (ABS) setup.', + enum: ['abs'], + }, + value: { + type: 'string', + description: + 'List of different representations you want to create separated by an underscore.\n', + }, + }, + required: ['protocol', 'type', 'value'], + }, + ], + }, + }, + pre: { + type: 'string', + description: + 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', + }, + }, + }, + useUniqueFileName: { + type: 'boolean', + description: + 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + required: ['file', 'fileName'], + $defs: { + extensions: { + type: 'array', + title: 'Extensions Array', + description: + 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + }, + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.beta.v2.files.upload(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts new file mode 100644 index 00000000..f3eaf25b --- /dev/null +++ b/packages/mcp-server/src/tools/cache/invalidation/create-cache-invalidation.ts @@ -0,0 +1,46 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'cache.invalidation', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/purge', + operationId: 'purge-cache', +}; + +export const tool: Tool = { + name: 'create_cache_invalidation', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API will purge CDN cache and ImageKit.io's internal cache for a file. Note: Purge cache is an asynchronous process and it may take some time to reflect the changes.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n requestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + url: { + type: 'string', + description: 'The full URL of the file to be purged.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['url'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts new file mode 100644 index 00000000..2cb17484 --- /dev/null +++ b/packages/mcp-server/src/tools/cache/invalidation/get-cache-invalidation.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'cache.invalidation', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/purge/{requestId}', + operationId: 'purge-status', +}; + +export const tool: Tool = { + name: 'get_cache_invalidation', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a purge cache request.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'Status of the purge request.',\n enum: [ 'Pending',\n 'Completed'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + requestId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['requestId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { requestId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.cache.invalidation.get(requestId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts new file mode 100644 index 00000000..7cd2d2e9 --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/create-custom-metadata-fields.ts @@ -0,0 +1,154 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/customMetadataFields', + operationId: 'create-new-field', +}; + +export const tool: Tool = { + name: 'create_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API creates a new custom metadata field. Once a custom metadata field is created either through this API or using the dashboard UI, its value can be set on the assets. The value of a field for an asset can be set using the media library UI or programmatically through upload or update assets API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + label: { + type: 'string', + description: + 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI.', + }, + name: { + type: 'string', + description: + 'API name of the custom metadata field. This should be unique across all (including deleted) custom metadata fields.', + }, + schema: { + type: 'object', + properties: { + type: { + type: 'string', + description: 'Type of the custom metadata field.', + enum: ['Text', 'Textarea', 'Number', 'Date', 'Boolean', 'SingleSelect', 'MultiSelect'], + }, + defaultValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + { + type: 'array', + title: 'Mixed', + description: + 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + ], + description: + 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', + }, + isValueRequired: { + type: 'boolean', + description: + 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', + }, + maxLength: { + type: 'number', + description: + 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + maxValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + minLength: { + type: 'number', + description: + 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + minValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + selectOptions: { + type: 'array', + description: + 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + }, + required: ['type'], + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['label', 'name', 'schema'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts new file mode 100644 index 00000000..35adacb2 --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/delete-custom-metadata-fields.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/customMetadataFields/{id}', + operationId: 'delete-a-field', +}; + +export const tool: Tool = { + name: 'delete_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a custom metadata field. Even after deleting a custom metadata field, you cannot create any new custom metadata field with the same name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.delete(id))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts new file mode 100644 index 00000000..d85c214f --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/list-custom-metadata-fields.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/customMetadataFields', + operationId: 'list-all-fields', +}; + +export const tool: Tool = { + name: 'list_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the array of created custom metadata field objects. By default the API returns only non deleted field objects, but you can include deleted fields in the API response.\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/custom_metadata_field'\n },\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + includeDeleted: { + type: 'boolean', + description: 'Set it to `true` to include deleted field objects in the API response.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: [], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.customMetadataFields.list(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts new file mode 100644 index 00000000..7322540f --- /dev/null +++ b/packages/mcp-server/src/tools/custom-metadata-fields/update-custom-metadata-fields.ts @@ -0,0 +1,150 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'customMetadataFields', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/v1/customMetadataFields/{id}', + operationId: 'update-existing-field', +}; + +export const tool: Tool = { + name: 'update_custom_metadata_fields', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API updates the label or schema of an existing custom metadata field.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/custom_metadata_field',\n $defs: {\n custom_metadata_field: {\n type: 'object',\n description: 'Object containing details of a custom metadata field.',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier for the custom metadata field. Use this to update the field.'\n },\n label: {\n type: 'string',\n description: 'Human readable name of the custom metadata field. This name is displayed as form field label to the users while setting field value on the asset in the media library UI.\\n'\n },\n name: {\n type: 'string',\n description: 'API name of the custom metadata field. This becomes the key while setting `customMetadata` (key-value object) for an asset using upload or update API.\\n'\n },\n schema: {\n type: 'object',\n description: 'An object that describes the rules for the custom metadata field value.',\n properties: {\n type: {\n type: 'string',\n description: 'Type of the custom metadata field.',\n enum: [ 'Text',\n 'Textarea',\n 'Number',\n 'Date',\n 'Boolean',\n 'SingleSelect',\n 'MultiSelect'\n ]\n },\n defaultValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n },\n {\n type: 'array',\n title: 'Mixed',\n description: 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n ],\n description: 'The default value for this custom metadata field. Date type of default value depends on the field type.\\n'\n },\n isValueRequired: {\n type: 'boolean',\n description: 'Specifies if the this custom metadata field is required or not.\\n'\n },\n maxLength: {\n type: 'number',\n description: 'Maximum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n maxValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Maximum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n minLength: {\n type: 'number',\n description: 'Minimum length of string. Only set if `type` is set to `Text` or `Textarea`.\\n'\n },\n minValue: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n }\n ],\n description: 'Minimum value of the field. Only set if field type is `Date` or `Number`. For `Date` type field, the value will be in ISO8601 string format. For `Number` type field, it will be a numeric value.\\n'\n },\n selectOptions: {\n type: 'array',\n description: 'An array of allowed values when field type is `SingleSelect` or `MultiSelect`.\\n',\n items: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'number'\n },\n {\n type: 'boolean'\n }\n ]\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n required: [ 'id',\n 'label',\n 'name',\n 'schema'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + label: { + type: 'string', + description: + 'Human readable name of the custom metadata field. This should be unique across all non deleted custom metadata fields. This name is displayed as form field label to the users while setting field value on an asset in the media library UI. This parameter is required if `schema` is not provided.', + }, + schema: { + type: 'object', + description: + 'An object that describes the rules for the custom metadata key. This parameter is required if `label` is not provided. Note: `type` cannot be updated and will be ignored if sent with the `schema`. The schema will be validated as per the existing `type`.\n', + properties: { + defaultValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + { + type: 'array', + title: 'Mixed', + description: + 'Default value should be of type array when custom metadata field type is set to `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + ], + description: + 'The default value for this custom metadata field. This property is only required if `isValueRequired` property is set to `true`. The value should match the `type` of custom metadata field.\n', + }, + isValueRequired: { + type: 'boolean', + description: + 'Sets this custom metadata field as required. Setting custom metadata fields on an asset will throw error if the value for all required fields are not present in upload or update asset API request body.\n', + }, + maxLength: { + type: 'number', + description: + 'Maximum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + maxValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Maximum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + minLength: { + type: 'number', + description: + 'Minimum length of string. Only set this property if `type` is set to `Text` or `Textarea`.\n', + }, + minValue: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + description: + 'Minimum value of the field. Only set this property if field type is `Date` or `Number`. For `Date` type field, set the minimum date in ISO8601 string format. For `Number` type field, set the minimum numeric value.\n', + }, + selectOptions: { + type: 'array', + description: + 'An array of allowed values. This property is only required if `type` property is set to `SingleSelect` or `MultiSelect`.\n', + items: { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['id'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { id, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.customMetadataFields.update(id, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts new file mode 100644 index 00000000..df867862 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/add-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/addTags', + operationId: 'add-tags-bulk', +}; + +export const tool: Tool = { + name: 'add_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API adds tags to multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully added.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds to which you want to add tags.\n', + items: { + type: 'string', + }, + }, + tags: { + type: 'array', + description: 'An array of tags that you want to add to the files.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds', 'tags'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.addTags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts new file mode 100644 index 00000000..15f058f8 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/delete-files-bulk.ts @@ -0,0 +1,49 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/batch/deleteByFileIds', + operationId: 'delete-multiple-files', +}; + +export const tool: Tool = { + name: 'delete_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes multiple files and all their file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n\nA maximum of 100 files can be deleted at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyDeletedFileIds: {\n type: 'array',\n description: 'An array of fileIds that were successfully deleted.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds which you want to delete.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.delete(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts new file mode 100644 index 00000000..3ab353ce --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/remove-ai-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/removeAITags', + operationId: 'remove-ai-tags-bulk', +}; + +export const tool: Tool = { + name: 'remove_ai_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes AITags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which AITags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + AITags: { + type: 'array', + description: 'An array of AITags that you want to remove from the files.\n', + items: { + type: 'string', + }, + }, + fileIds: { + type: 'array', + description: 'An array of fileIds from which you want to remove AITags.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['AITags', 'fileIds'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeAITags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts new file mode 100644 index 00000000..955898a6 --- /dev/null +++ b/packages/mcp-server/src/tools/files/bulk/remove-tags-files-bulk.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.bulk', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/removeTags', + operationId: 'remove-tags-bulk', +}; + +export const tool: Tool = { + name: 'remove_tags_files_bulk', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API removes tags from multiple files in bulk. A maximum of 50 files can be specified at a time.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n successfullyUpdatedFileIds: {\n type: 'array',\n description: 'An array of fileIds that in which tags were successfully removed.\\n',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileIds: { + type: 'array', + description: 'An array of fileIds from which you want to remove tags.\n', + items: { + type: 'string', + }, + }, + tags: { + type: 'array', + description: 'An array of tags that you want to remove from the files.\n', + items: { + type: 'string', + }, + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileIds', 'tags'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.bulk.removeTags(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/copy-files.ts b/packages/mcp-server/src/tools/files/copy-files.ts new file mode 100644 index 00000000..94a5e514 --- /dev/null +++ b/packages/mcp-server/src/tools/files/copy-files.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/copy', + operationId: 'copy-file', +}; + +export const tool: Tool = { + name: 'copy_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy a file from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions (if `includeFileVersions` is set to true) will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the folder you want to copy the above file into.\n', + }, + sourceFilePath: { + type: 'string', + description: 'The full path of the file you want to copy.\n', + }, + includeFileVersions: { + type: 'boolean', + description: + 'Option to copy all versions of a file. By default, only the current version of the file is copied. When set to true, all versions of the file will be copied. Default value - `false`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFilePath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.copy(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/delete-files.ts b/packages/mcp-server/src/tools/files/delete-files.ts new file mode 100644 index 00000000..61f46f2d --- /dev/null +++ b/packages/mcp-server/src/tools/files/delete-files.ts @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/files/{fileId}', + operationId: 'delete-file', +}; + +export const tool: Tool = { + name: 'delete_files', + description: + 'This API deletes the file and all its file versions permanently.\n\nNote: If a file or specific transformation has been requested in the past, then the response is cached. Deleting a file does not purge the cache. You can purge the cache using purge cache API.\n', + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + }, + required: ['fileId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, ...body } = args as any; + const response = await client.files.delete(fileId).asResponse(); + return asTextContentResult(await response.text()); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/get-files.ts b/packages/mcp-server/src/tools/files/get-files.ts new file mode 100644 index 00000000..4f9b26dd --- /dev/null +++ b/packages/mcp-server/src/tools/files/get-files.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/details', + operationId: 'get-file-details', +}; + +export const tool: Tool = { + name: 'get_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes about the current version of the file.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.get(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts new file mode 100644 index 00000000..39b1b033 --- /dev/null +++ b/packages/mcp-server/src/tools/files/metadata/get-files-metadata.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.metadata', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/metadata', + operationId: 'get-uploaded-file-metadata', +}; + +export const tool: Tool = { + name: 'get_files_metadata', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can programmatically get image EXIF, pHash, and other metadata for uploaded files in the ImageKit.io media library using this API.\n\nYou can also get the metadata in upload API response by passing `metadata` in `responseFields` parameter.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.get(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts new file mode 100644 index 00000000..09386d6e --- /dev/null +++ b/packages/mcp-server/src/tools/files/metadata/get-from-url-files-metadata.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.metadata', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/metadata', + operationId: 'get-metadata-from-url', +}; + +export const tool: Tool = { + name: 'get_from_url_files_metadata', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet image EXIF, pHash, and other metadata from ImageKit.io powered remote URL using this API.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/metadata',\n $defs: {\n metadata: {\n type: 'object',\n description: 'JSON object containing metadata.',\n properties: {\n audioCodec: {\n type: 'string',\n description: 'The audio codec used in the video (only for video).'\n },\n bitRate: {\n type: 'integer',\n description: 'The bit rate of the video in kbps (only for video).'\n },\n density: {\n type: 'integer',\n description: 'The density of the image in DPI.'\n },\n duration: {\n type: 'integer',\n description: 'The duration of the video in seconds (only for video).'\n },\n exif: {\n type: 'object',\n properties: {\n exif: {\n type: 'object',\n description: 'Object containing Exif details.',\n properties: {\n ApertureValue: {\n type: 'number'\n },\n ColorSpace: {\n type: 'integer'\n },\n CreateDate: {\n type: 'string'\n },\n CustomRendered: {\n type: 'integer'\n },\n DateTimeOriginal: {\n type: 'string'\n },\n ExifImageHeight: {\n type: 'integer'\n },\n ExifImageWidth: {\n type: 'integer'\n },\n ExifVersion: {\n type: 'string'\n },\n ExposureCompensation: {\n type: 'number'\n },\n ExposureMode: {\n type: 'integer'\n },\n ExposureProgram: {\n type: 'integer'\n },\n ExposureTime: {\n type: 'number'\n },\n Flash: {\n type: 'integer'\n },\n FlashpixVersion: {\n type: 'string'\n },\n FNumber: {\n type: 'number'\n },\n FocalLength: {\n type: 'integer'\n },\n FocalPlaneResolutionUnit: {\n type: 'integer'\n },\n FocalPlaneXResolution: {\n type: 'number'\n },\n FocalPlaneYResolution: {\n type: 'number'\n },\n InteropOffset: {\n type: 'integer'\n },\n ISO: {\n type: 'integer'\n },\n MeteringMode: {\n type: 'integer'\n },\n SceneCaptureType: {\n type: 'integer'\n },\n ShutterSpeedValue: {\n type: 'number'\n },\n SubSecTime: {\n type: 'string'\n },\n WhiteBalance: {\n type: 'integer'\n }\n }\n },\n gps: {\n type: 'object',\n description: 'Object containing GPS information.',\n properties: {\n GPSVersionID: {\n type: 'array',\n items: {\n type: 'integer'\n }\n }\n }\n },\n image: {\n type: 'object',\n description: 'Object containing EXIF image information.',\n properties: {\n ExifOffset: {\n type: 'integer'\n },\n GPSInfo: {\n type: 'integer'\n },\n Make: {\n type: 'string'\n },\n Model: {\n type: 'string'\n },\n ModifyDate: {\n type: 'string'\n },\n Orientation: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n Software: {\n type: 'string'\n },\n XResolution: {\n type: 'integer'\n },\n YCbCrPositioning: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n },\n interoperability: {\n type: 'object',\n description: 'JSON object.',\n properties: {\n InteropIndex: {\n type: 'string'\n },\n InteropVersion: {\n type: 'string'\n }\n }\n },\n makernote: {\n type: 'object',\n additionalProperties: true\n },\n thumbnail: {\n type: 'object',\n description: 'Object containing Thumbnail information.',\n properties: {\n Compression: {\n type: 'integer'\n },\n ResolutionUnit: {\n type: 'integer'\n },\n ThumbnailLength: {\n type: 'integer'\n },\n ThumbnailOffset: {\n type: 'integer'\n },\n XResolution: {\n type: 'integer'\n },\n YResolution: {\n type: 'integer'\n }\n }\n }\n }\n },\n format: {\n type: 'string',\n description: 'The format of the file (e.g., \\'jpg\\', \\'mp4\\').'\n },\n hasColorProfile: {\n type: 'boolean',\n description: 'Indicates if the image has a color profile.'\n },\n hasTransparency: {\n type: 'boolean',\n description: 'Indicates if the image contains transparent areas.'\n },\n height: {\n type: 'integer',\n description: 'The height of the image or video in pixels.'\n },\n pHash: {\n type: 'string',\n description: 'Perceptual hash of the image.'\n },\n quality: {\n type: 'integer',\n description: 'The quality indicator of the image.'\n },\n size: {\n type: 'integer',\n description: 'The file size in bytes.'\n },\n videoCodec: {\n type: 'string',\n description: 'The video codec used in the video (only for video).'\n },\n width: {\n type: 'integer',\n description: 'The width of the image or video in pixels.'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + url: { + type: 'string', + description: 'Should be a valid file URL. It should be accessible using your ImageKit.io account.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['url'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.metadata.getFromURL(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/move-files.ts b/packages/mcp-server/src/tools/files/move-files.ts new file mode 100644 index 00000000..6a8e36de --- /dev/null +++ b/packages/mcp-server/src/tools/files/move-files.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/files/move', + operationId: 'move-file', +}; + +export const tool: Tool = { + name: 'move_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move a file and all its versions from one folder to another. \n\nNote: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the folder you want to move the above file into.\n', + }, + sourceFilePath: { + type: 'string', + description: 'The full path of the file you want to move.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFilePath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.move(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/rename-files.ts b/packages/mcp-server/src/tools/files/rename-files.ts new file mode 100644 index 00000000..3bd46bb3 --- /dev/null +++ b/packages/mcp-server/src/tools/files/rename-files.ts @@ -0,0 +1,58 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/files/rename', + operationId: 'rename-file', +}; + +export const tool: Tool = { + name: 'rename_files', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nYou can rename an already existing file in the media library using rename file API. This operation would rename all file versions of the file. \n\nNote: The old URLs will stop working. The file/file version URLs cached on CDN will continue to work unless a purge is requested.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This can be used to check the status of the purge request.\\n'\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + filePath: { + type: 'string', + description: 'The full path of the file you want to rename.\n', + }, + newFileName: { + type: 'string', + description: + 'The new name of the file. A filename can contain:\n\nAlphanumeric Characters: `a-z`, `A-Z`, `0-9` (including Unicode letters, marks, and numerals in other languages).\nSpecial Characters: `.`, `_`, and `-`.\n\nAny other character, including space, will be replaced by `_`.\n', + }, + purgeCache: { + type: 'boolean', + description: + "Option to purge cache for the old file and its versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove cached content of old file and its versions. This purge request is counted against your monthly purge quota.\n\nNote: If the old file were accessible at `https://ik.imagekit.io/demo/old-filename.jpg`, a purge cache request would be issued against `https://ik.imagekit.io/demo/old-filename.jpg*` (with a wildcard at the end). It will remove the file and its versions' URLs and any transformations made using query parameters on this file or its versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\n\n\nDefault value - `false`\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['filePath', 'newFileName'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.rename(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/update-files.ts b/packages/mcp-server/src/tools/files/update-files.ts new file mode 100644 index 00000000..a8113419 --- /dev/null +++ b/packages/mcp-server/src/tools/files/update-files.ts @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'patch', + httpPath: '/v1/files/{fileId}/details', + operationId: 'update-file-details', +}; + +export const tool: Tool = { + name: 'update_files', + description: + 'This API updates the details or attributes of the current version of the file. You can update `tags`, `customCoordinates`, `customMetadata`, publication status, remove existing `AITags` and apply extensions using this API.\n', + inputSchema: { + type: 'object', + anyOf: [ + { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image in the format `x,y,width,height` e.g. `10,10,100,100`. Send `null` to unset this value.\n', + }, + customMetadata: { + type: 'object', + description: + 'A key-value data to be associated with the asset. To unset a key, send `null` value for that key. Before setting any custom metadata on an asset you have to create the field using custom metadata fields API.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + extensions: { + $ref: '#/$defs/extensions', + }, + removeAITags: { + anyOf: [ + { + type: 'array', + items: { + type: 'string', + }, + }, + { + type: 'string', + enum: ['all'], + }, + ], + description: + 'An array of AITags associated with the file that you want to remove, e.g. `["car", "vehicle", "motorsports"]`. \n\nIf you want to remove all AITags associated with the file, send a string - "all".\n\nNote: The remove operation for `AITags` executes before any of the `extensions` are processed.\n', + }, + tags: { + type: 'array', + description: + 'An array of tags associated with the file, such as `["tag1", "tag2"]`. Send `null` to unset all tags associated with the file.\n', + items: { + type: 'string', + }, + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + required: ['fileId'], + }, + { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + publish: { + type: 'object', + description: 'Configure the publication status of a file and its versions.\n', + properties: { + isPublished: { + type: 'boolean', + description: 'Set to `true` to publish the file. Set to `false` to unpublish the file.\n', + }, + includeFileVersions: { + type: 'boolean', + description: + 'Set to `true` to publish/unpublish all versions of the file. Set to `false` to publish/unpublish only the current version of the file.\n', + }, + }, + required: ['isPublished'], + }, + }, + required: ['fileId'], + }, + ], + $defs: { + extensions: { + type: 'array', + title: 'Extensions Array', + description: + 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + }, + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, ...body } = args as any; + return asTextContentResult(await client.files.update(fileId, body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/upload-files.ts b/packages/mcp-server/src/tools/files/upload-files.ts new file mode 100644 index 00000000..9173a4d8 --- /dev/null +++ b/packages/mcp-server/src/tools/files/upload-files.ts @@ -0,0 +1,331 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/api/v1/files/upload', + operationId: 'upload-file', +}; + +export const tool: Tool = { + name: 'upload_files', + description: + 'ImageKit.io allows you to upload files directly from both the server and client sides. For server-side uploads, private API key authentication is used. For client-side uploads, generate a one-time `token`, `signature`, and `expire` from your secure backend using private API. [Learn more](/docs/api-reference/upload-file/upload-file#how-to-implement-client-side-file-upload) about how to implement client-side file upload.\n\nThe [V2 API](/docs/api-reference/upload-file/upload-file-v2) enhances security by verifying the entire payload using JWT.\n\n**File size limit** \\\nOn the free plan, the maximum upload file sizes are 20MB for images, audio, and raw files and 100MB for videos. On the paid plan, these limits increase to 40MB for images, audio, and raw files and 2GB for videos. These limits can be further increased with higher-tier plans.\n\n**Version limit** \\\nA file can have a maximum of 100 versions.\n\n**Demo applications**\n\n- A full-fledged [upload widget using Uppy](https://github.com/imagekit-samples/uppy-uploader), supporting file selections from local storage, URL, Dropbox, Google Drive, Instagram, and more.\n- [Quick start guides](/docs/quick-start-guides) for various frameworks and technologies.\n', + inputSchema: { + type: 'object', + properties: { + file: { + type: 'string', + description: + 'The API accepts any of the following:\n\n- **Binary data** – send the raw bytes as `multipart/form-data`.\n- **HTTP / HTTPS URL** – a publicly reachable URL that ImageKit’s servers can fetch.\n- **Base64 string** – the file encoded as a Base64 data URI or plain Base64.\n\nWhen supplying a URL, the server must receive the response headers within 8 seconds; otherwise the request fails with 400 Bad Request.\n', + }, + fileName: { + type: 'string', + description: + 'The name with which the file has to be uploaded.\nThe file name can contain:\n\n - Alphanumeric Characters: `a-z`, `A-Z`, `0-9`.\n - Special Characters: `.`, `-`\n\nAny other character including space will be replaced by `_`\n', + }, + token: { + type: 'string', + description: + 'A unique value that the ImageKit.io server will use to recognize and prevent subsequent retries for the same request. We suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. This field is only required for authentication when uploading a file from the client side.\n\n**Note**: Sending a value that has been used in the past will result in a validation error. Even if your previous request resulted in an error, you should always send a new value for this field.\n', + }, + checks: { + type: 'string', + description: + 'Server-side checks to run on the asset.\nRead more about [Upload API checks](/docs/api-reference/upload-file/upload-file#upload-api-checks).\n', + }, + customCoordinates: { + type: 'string', + description: + 'Define an important area in the image. This is only relevant for image type files.\n\n - To be passed as a string with the x and y coordinates of the top-left corner, and width and height of the area of interest in the format `x,y,width,height`. For example - `10,10,100,100`\n - Can be used with fo-customtransformation.\n - If this field is not specified and the file is overwritten, then customCoordinates will be removed.\n', + }, + customMetadata: { + type: 'object', + description: + 'JSON key-value pairs to associate with the asset. Create the custom metadata fields before setting these values.\n', + additionalProperties: true, + }, + description: { + type: 'string', + description: 'Optional text to describe the contents of the file.\n', + }, + expire: { + type: 'integer', + description: + 'The time until your signature is valid. It must be a [Unix time](https://en.wikipedia.org/wiki/Unix_time) in less than 1 hour into the future. It should be in seconds. This field is only required for authentication when uploading a file from the client side.\n', + }, + extensions: { + $ref: '#/$defs/extensions', + }, + folder: { + type: 'string', + description: + "The folder path in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created.\n\nThe folder name can contain:\n\n - Alphanumeric Characters: `a-z` , `A-Z` , `0-9`\n - Special Characters: `/` , `_` , `-`\n\nUsing multiple `/` creates a nested folder.\n", + }, + isPrivateFile: { + type: 'boolean', + description: + 'Whether to mark the file as private or not.\n\nIf `true`, the file is marked as private and is accessible only using named transformation or signed URL.\n', + }, + isPublished: { + type: 'boolean', + description: + 'Whether to upload file as published or not.\n\nIf `false`, the file is marked as unpublished, which restricts access to the file only via the media library. Files in draft or unpublished state can only be publicly accessed after being published.\n\nThe option to upload in draft state is only available in custom enterprise pricing plans.\n', + }, + overwriteAITags: { + type: 'boolean', + description: + 'If set to `true` and a file already exists at the exact location, its AITags will be removed. Set `overwriteAITags` to `false` to preserve AITags.\n', + }, + overwriteCustomMetadata: { + type: 'boolean', + description: + 'If the request does not have `customMetadata`, and a file already exists at the exact location, existing customMetadata will be removed.\n', + }, + overwriteFile: { + type: 'boolean', + description: + 'If `false` and `useUniqueFileName` is also `false`, and a file already exists at the exact location, upload API will return an error immediately.\n', + }, + overwriteTags: { + type: 'boolean', + description: + 'If the request does not have `tags`, and a file already exists at the exact location, existing tags will be removed.\n', + }, + publicKey: { + type: 'string', + description: + 'Your ImageKit.io public key. This field is only required for authentication when uploading a file from the client side.\n', + }, + responseFields: { + type: 'array', + description: 'Array of response field keys to include in the API response body.\n', + items: { + type: 'string', + enum: [ + 'tags', + 'customCoordinates', + 'isPrivateFile', + 'embeddedMetadata', + 'isPublished', + 'customMetadata', + 'metadata', + ], + }, + }, + signature: { + type: 'string', + description: + 'HMAC-SHA1 digest of the token+expire using your ImageKit.io private API key as a key. Learn how to create a signature on the page below. This should be in lowercase.\n\nSignature must be calculated on the server-side. This field is only required for authentication when uploading a file from the client side.\n', + }, + tags: { + type: 'array', + description: + 'Set the tags while uploading the file.\nProvide an array of tag strings (e.g. `["tag1", "tag2", "tag3"]`). The combined length of all tag characters must not exceed 500, and the `%` character is not allowed.\nIf this field is not specified and the file is overwritten, the existing tags will be removed.\n', + items: { + type: 'string', + }, + }, + transformation: { + type: 'object', + description: + "Configure pre-processing (`pre`) and post-processing (`post`) transformations.\n\n- `pre` — applied before the file is uploaded to the Media Library. \n Useful for reducing file size or applying basic optimizations upfront (e.g., resize, compress).\n\n- `post` — applied immediately after upload. \n Ideal for generating transformed versions (like video encodes or thumbnails) in advance, so they're ready for delivery without delay.\n\nYou can mix and match any combination of post-processing types.\n", + properties: { + post: { + type: 'array', + description: + 'List of transformations to apply *after* the file is uploaded. \nEach item must match one of the following types:\n`transformation`, `gif-to-video`, `thumbnail`, `abs`.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Simple post-transformation', + properties: { + type: { + type: 'string', + description: 'Transformation type.', + enum: ['transformation'], + }, + value: { + type: 'string', + description: + 'Transformation string (e.g. `w-200,h-200`). \nSame syntax as ImageKit URL-based transformations.\n', + }, + }, + required: ['type', 'value'], + }, + { + type: 'object', + title: 'Convert GIF to video', + properties: { + type: { + type: 'string', + description: 'Converts an animated GIF into an MP4.', + enum: ['gif-to-video'], + }, + value: { + type: 'string', + description: + 'Optional transformation string to apply to the output video. \n**Example**: `q-80`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Generate a thumbnail', + properties: { + type: { + type: 'string', + description: 'Generates a thumbnail image.', + enum: ['thumbnail'], + }, + value: { + type: 'string', + description: 'Optional transformation string. \n**Example**: `w-150,h-150`\n', + }, + }, + required: ['type'], + }, + { + type: 'object', + title: 'Adaptive Bitrate Streaming', + properties: { + protocol: { + type: 'string', + description: 'Streaming protocol to use (`hls` or `dash`).', + enum: ['hls', 'dash'], + }, + type: { + type: 'string', + description: 'Adaptive Bitrate Streaming (ABS) setup.', + enum: ['abs'], + }, + value: { + type: 'string', + description: + 'List of different representations you want to create separated by an underscore.\n', + }, + }, + required: ['protocol', 'type', 'value'], + }, + ], + }, + }, + pre: { + type: 'string', + description: + 'Transformation string to apply before uploading the file to the Media Library. Useful for optimizing files at ingestion.\n', + }, + }, + }, + useUniqueFileName: { + type: 'boolean', + description: + 'Whether to use a unique filename for this file or not.\n\nIf `true`, ImageKit.io will add a unique suffix to the filename parameter to get a unique filename.\n\nIf `false`, then the image is uploaded with the provided filename parameter, and any existing file with the same name is replaced.\n', + }, + webhookUrl: { + type: 'string', + description: + 'The final status of extensions after they have completed execution will be delivered to this endpoint as a POST request. [Learn more](/docs/api-reference/digital-asset-management-dam/managing-assets/update-file-details#webhook-payload-structure) about the webhook payload structure.\n', + }, + }, + required: ['file', 'fileName'], + $defs: { + extensions: { + type: 'array', + title: 'Extensions Array', + description: + 'Array of extensions to be applied to the asset. Each extension can be configured with specific parameters based on the extension type.\n', + items: { + anyOf: [ + { + type: 'object', + title: 'Remove background', + properties: { + name: { + type: 'string', + description: 'Specifies the background removal extension.', + enum: ['remove-bg'], + }, + options: { + type: 'object', + properties: { + add_shadow: { + type: 'boolean', + description: + 'Whether to add an artificial shadow to the result. Default is false. Note: Adding shadows is currently only supported for car photos.\n', + }, + bg_color: { + type: 'string', + description: + 'Specifies a solid color background using hex code (e.g., "81d4fa", "fff") or color name (e.g., "green"). If this parameter is set, `bg_image_url` must be empty.\n', + }, + bg_image_url: { + type: 'string', + description: + 'Sets a background image from a URL. If this parameter is set, `bg_color` must be empty.\n', + }, + semitransparency: { + type: 'boolean', + description: + 'Allows semi-transparent regions in the result. Default is true. Note: Semitransparency is currently only supported for car windows.\n', + }, + }, + }, + }, + required: ['name'], + }, + { + type: 'object', + title: 'Auto tagging', + properties: { + maxTags: { + type: 'integer', + description: 'Maximum number of tags to attach to the asset.', + }, + minConfidence: { + type: 'integer', + description: 'Minimum confidence level for tags to be considered valid.', + }, + name: { + type: 'string', + description: 'Specifies the auto-tagging extension used.', + enum: ['google-auto-tagging', 'aws-auto-tagging'], + }, + }, + required: ['maxTags', 'minConfidence', 'name'], + }, + { + type: 'object', + title: 'Auto description', + properties: { + name: { + type: 'string', + description: 'Specifies the auto description extension.', + enum: ['ai-auto-description'], + }, + }, + required: ['name'], + }, + ], + }, + }, + }, + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.files.upload(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts new file mode 100644 index 00000000..f8a75c37 --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/delete-files-versions.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/files/{fileId}/versions/{versionId}', + operationId: 'delete-file-version', +}; + +export const tool: Tool = { + name: 'delete_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API deletes a non-current file version permanently. The API returns an empty response.\n\nNote: If you want to delete all versions of a file, use the delete file API.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.delete(versionId, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/get-files-versions.ts b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts new file mode 100644 index 00000000..22b68d8e --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/get-files-versions.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/versions/{versionId}', + operationId: 'get-file-version-details', +}; + +export const tool: Tool = { + name: 'get_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns an object with details or attributes of a file version.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.get(versionId, body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/list-files-versions.ts b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts new file mode 100644 index 00000000..aa6d5d2b --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/list-files-versions.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/files/{fileId}/versions', + operationId: 'list-file-versions', +}; + +export const tool: Tool = { + name: 'list_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns details of all versions of a file.\n\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/file'\n },\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { fileId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.files.versions.list(fileId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts new file mode 100644 index 00000000..4e9e9197 --- /dev/null +++ b/packages/mcp-server/src/tools/files/versions/restore-files-versions.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'files.versions', + operation: 'write', + tags: [], + httpMethod: 'put', + httpPath: '/v1/files/{fileId}/versions/{versionId}/restore', + operationId: 'restore-file-version', +}; + +export const tool: Tool = { + name: 'restore_files_versions', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API restores a file version as the current file version.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/file',\n $defs: {\n file: {\n type: 'object',\n title: 'File & File Version',\n description: 'Object containing details of a file or file version.',\n properties: {\n AITags: {\n type: 'array',\n description: 'An array of tags assigned to the file by auto tagging.\\n',\n items: {\n type: 'object',\n properties: {\n confidence: {\n type: 'number',\n description: 'Confidence score of the tag.'\n },\n name: {\n type: 'string',\n description: 'Name of the tag.'\n },\n source: {\n type: 'string',\n description: 'Source of the tag. Possible values are `google-auto-tagging` and `aws-auto-tagging`.'\n }\n }\n }\n },\n createdAt: {\n type: 'string',\n description: 'Date and time when the file was uploaded. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n customCoordinates: {\n type: 'string',\n description: 'An string with custom coordinates of the file.\\n'\n },\n customMetadata: {\n type: 'object',\n description: 'An object with custom metadata for the file.\\n',\n additionalProperties: true\n },\n description: {\n type: 'string',\n description: 'Optional text to describe the contents of the file. Can be set by the user or the ai-auto-description extension.\\n'\n },\n fileId: {\n type: 'string',\n description: 'Unique identifier of the asset.'\n },\n filePath: {\n type: 'string',\n description: 'Path of the file. This is the path you would use in the URL to access the file. For example, if the file is at the root of the media library, the path will be `/file.jpg`. If the file is inside a folder named `images`, the path will be `/images/file.jpg`.\\n'\n },\n fileType: {\n type: 'string',\n description: 'Type of the file. Possible values are `image`, `non-image`.\\n'\n },\n hasAlpha: {\n type: 'boolean',\n description: 'Specifies if the image has an alpha channel.\\n'\n },\n height: {\n type: 'number',\n description: 'Height of the file.\\n'\n },\n isPrivateFile: {\n type: 'boolean',\n description: 'Specifies if the file is private or not.\\n'\n },\n isPublished: {\n type: 'boolean',\n description: 'Specifies if the file is published or not.\\n'\n },\n mime: {\n type: 'string',\n description: 'MIME type of the file.\\n'\n },\n name: {\n type: 'string',\n description: 'Name of the asset.'\n },\n size: {\n type: 'number',\n description: 'Size of the file in bytes.\\n'\n },\n tags: {\n type: 'array',\n description: 'An array of tags assigned to the file. Tags are used to search files in the media library.\\n',\n items: {\n type: 'string'\n }\n },\n thumbnail: {\n type: 'string',\n description: 'URL of the thumbnail image. This URL is used to access the thumbnail image of the file in the media library.\\n'\n },\n type: {\n type: 'string',\n description: 'Type of the asset.',\n enum: [ 'file',\n 'file-version'\n ]\n },\n updatedAt: {\n type: 'string',\n description: 'Date and time when the file was last updated. The date and time is in ISO8601 format.\\n',\n format: 'date-time'\n },\n url: {\n type: 'string',\n description: 'URL of the file.\\n'\n },\n versionInfo: {\n type: 'object',\n description: 'An object with details of the file version.\\n',\n properties: {\n id: {\n type: 'string',\n description: 'Unique identifier of the file version.'\n },\n name: {\n type: 'string',\n description: 'Name of the file version.'\n }\n }\n },\n width: {\n type: 'number',\n description: 'Width of the file.\\n'\n }\n }\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + fileId: { + type: 'string', + }, + versionId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['fileId', 'versionId'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { versionId, jq_filter, ...body } = args as any; + return asTextContentResult( + await maybeFilter(jq_filter, await client.files.versions.restore(versionId, body)), + ); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/copy-folders.ts b/packages/mcp-server/src/tools/folders/copy-folders.ts new file mode 100644 index 00000000..17dd44b8 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/copy-folders.ts @@ -0,0 +1,55 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/copyFolder', + operationId: 'copy-folder', +}; + +export const tool: Tool = { + name: 'copy_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will copy one folder into another. The selected folder, its nested folders, files, and their versions (in `includeVersions` is set to true) are copied in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the destination folder where you want to copy the source folder into.\n', + }, + sourceFolderPath: { + type: 'string', + description: 'The full path to the source folder you want to copy.\n', + }, + includeVersions: { + type: 'boolean', + description: + 'Option to copy all versions of files that are nested inside the selected folder. By default, only the current version of each file will be copied. When set to true, all versions of each file will be copied. Default value - `false`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.copy(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/create-folders.ts b/packages/mcp-server/src/tools/folders/create-folders.ts new file mode 100644 index 00000000..00bbe6fb --- /dev/null +++ b/packages/mcp-server/src/tools/folders/create-folders.ts @@ -0,0 +1,52 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/folder', + operationId: 'create-folder', +}; + +export const tool: Tool = { + name: 'create_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will create a new folder. You can specify the folder name and location of the parent folder where this new folder should be created.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderName: { + type: 'string', + description: + 'The folder will be created with this name. \n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) will be replaced by an underscore i.e. `_`.\n', + }, + parentFolderPath: { + type: 'string', + description: + "The folder where the new folder should be created, for root use `/` else the path e.g. `containing/folder/`.\n\nNote: If any folder(s) is not present in the parentFolderPath parameter, it will be automatically created. For example, if you pass `/product/images/summer`, then `product`, `images`, and `summer` folders will be created if they don't already exist.\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderName', 'parentFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.create(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/delete-folders.ts b/packages/mcp-server/src/tools/folders/delete-folders.ts new file mode 100644 index 00000000..65a2bb1a --- /dev/null +++ b/packages/mcp-server/src/tools/folders/delete-folders.ts @@ -0,0 +1,48 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'delete', + httpPath: '/v1/folder', + operationId: 'delete-folder', +}; + +export const tool: Tool = { + name: 'delete_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will delete a folder and all its contents permanently. The API returns an empty response.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {}\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderPath: { + type: 'string', + description: 'Full path to the folder you want to delete. For example `/folder/to/delete/`.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderPath'], + }, + annotations: { + idempotentHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.delete(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/job/get-folders-job.ts b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts new file mode 100644 index 00000000..982e5a24 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/job/get-folders-job.ts @@ -0,0 +1,47 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders.job', + operation: 'read', + tags: [], + httpMethod: 'get', + httpPath: '/v1/bulkJobs/{jobId}', + operationId: 'bulk-job-status', +}; + +export const tool: Tool = { + name: 'get_folders_job', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API returns the status of a bulk job like copy and move folder operations.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job.\\n'\n },\n purgeRequestId: {\n type: 'string',\n description: 'Unique identifier of the purge request. This will be present only if `purgeCache` is set to `true` in the rename folder API request.\\n'\n },\n status: {\n type: 'string',\n description: 'Status of the bulk job.',\n enum: [ 'Pending',\n 'Completed'\n ]\n },\n type: {\n type: 'string',\n description: 'Type of the bulk job.',\n enum: [ 'COPY_FOLDER',\n 'MOVE_FOLDER',\n 'RENAME_FOLDER'\n ]\n }\n }\n}\n```", + inputSchema: { + type: 'object', + properties: { + jobId: { + type: 'string', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['jobId'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jobId, jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.job.get(jobId))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/move-folders.ts b/packages/mcp-server/src/tools/folders/move-folders.ts new file mode 100644 index 00000000..503199ba --- /dev/null +++ b/packages/mcp-server/src/tools/folders/move-folders.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/moveFolder', + operationId: 'move-folder', +}; + +export const tool: Tool = { + name: 'move_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis will move one folder into another. The selected folder, its nested folders, files, and their versions are moved in this operation. Note: If any file at the destination has the same name as the source file, then the source file and its versions will be appended to the destination file version history.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + destinationPath: { + type: 'string', + description: 'Full path to the destination folder where you want to move the source folder into.\n', + }, + sourceFolderPath: { + type: 'string', + description: 'The full path to the source folder you want to move.\n', + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['destinationPath', 'sourceFolderPath'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.move(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/folders/rename-folders.ts b/packages/mcp-server/src/tools/folders/rename-folders.ts new file mode 100644 index 00000000..a77ca1b2 --- /dev/null +++ b/packages/mcp-server/src/tools/folders/rename-folders.ts @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { maybeFilter } from 'imagekit-api-mcp/filtering'; +import { Metadata, asTextContentResult } from 'imagekit-api-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import ImageKit from '@imagekit/nodejs'; + +export const metadata: Metadata = { + resource: 'folders', + operation: 'write', + tags: [], + httpMethod: 'post', + httpPath: '/v1/bulkJobs/renameFolder', + operationId: 'rename-folder', +}; + +export const tool: Tool = { + name: 'rename_folders', + description: + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis API allows you to rename an existing folder. The folder and all its nested assets and sub-folders will remain unchanged, but their paths will be updated to reflect the new folder name.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n title: 'Async Bulk Job Response',\n description: 'Job submitted successfully. A `jobId` will be returned.',\n properties: {\n jobId: {\n type: 'string',\n description: 'Unique identifier of the bulk job. This can be used to check the status of the bulk job.\\n'\n }\n },\n required: [ 'jobId'\n ]\n}\n```", + inputSchema: { + type: 'object', + properties: { + folderPath: { + type: 'string', + description: 'The full path to the folder you want to rename.\n', + }, + newFolderName: { + type: 'string', + description: + 'The new name for the folder.\n\nAll characters except alphabets and numbers (inclusive of unicode letters, marks, and numerals in other languages) and `-` will be replaced by an underscore i.e. `_`.\n', + }, + purgeCache: { + type: 'boolean', + description: + "Option to purge cache for the old nested files and their versions' URLs.\n\nWhen set to true, it will internally issue a purge cache request on CDN to remove the cached content of the old nested files and their versions. There will only be one purge request for all the nested files, which will be counted against your monthly purge quota.\n\nNote: A purge cache request will be issued against `https://ik.imagekit.io/old/folder/path*` (with a wildcard at the end). This will remove all nested files, their versions' URLs, and any transformations made using query parameters on these files or their versions. However, the cache for file transformations made using path parameters will persist. You can purge them using the purge API. For more details, refer to the purge API documentation.\n\nDefault value - `false`\n", + }, + jq_filter: { + type: 'string', + title: 'jq Filter', + description: + 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', + }, + }, + required: ['folderPath', 'newFolderName'], + }, + annotations: {}, +}; + +export const handler = async (client: ImageKit, args: Record | undefined) => { + const { jq_filter, ...body } = args as any; + return asTextContentResult(await maybeFilter(jq_filter, await client.folders.rename(body))); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts new file mode 100644 index 00000000..ba7083d7 --- /dev/null +++ b/packages/mcp-server/src/tools/index.ts @@ -0,0 +1,153 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, Endpoint, HandlerFunction } from './types'; + +export { Metadata, Endpoint, HandlerFunction }; + +import create_custom_metadata_fields from './custom-metadata-fields/create-custom-metadata-fields'; +import update_custom_metadata_fields from './custom-metadata-fields/update-custom-metadata-fields'; +import list_custom_metadata_fields from './custom-metadata-fields/list-custom-metadata-fields'; +import delete_custom_metadata_fields from './custom-metadata-fields/delete-custom-metadata-fields'; +import update_files from './files/update-files'; +import delete_files from './files/delete-files'; +import copy_files from './files/copy-files'; +import get_files from './files/get-files'; +import move_files from './files/move-files'; +import rename_files from './files/rename-files'; +import upload_files from './files/upload-files'; +import delete_files_bulk from './files/bulk/delete-files-bulk'; +import add_tags_files_bulk from './files/bulk/add-tags-files-bulk'; +import remove_ai_tags_files_bulk from './files/bulk/remove-ai-tags-files-bulk'; +import remove_tags_files_bulk from './files/bulk/remove-tags-files-bulk'; +import list_files_versions from './files/versions/list-files-versions'; +import delete_files_versions from './files/versions/delete-files-versions'; +import get_files_versions from './files/versions/get-files-versions'; +import restore_files_versions from './files/versions/restore-files-versions'; +import get_files_metadata from './files/metadata/get-files-metadata'; +import get_from_url_files_metadata from './files/metadata/get-from-url-files-metadata'; +import list_assets from './assets/list-assets'; +import create_cache_invalidation from './cache/invalidation/create-cache-invalidation'; +import get_cache_invalidation from './cache/invalidation/get-cache-invalidation'; +import create_folders from './folders/create-folders'; +import delete_folders from './folders/delete-folders'; +import copy_folders from './folders/copy-folders'; +import move_folders from './folders/move-folders'; +import rename_folders from './folders/rename-folders'; +import get_folders_job from './folders/job/get-folders-job'; +import get_accounts_usage from './accounts/usage/get-accounts-usage'; +import create_accounts_origins from './accounts/origins/create-accounts-origins'; +import update_accounts_origins from './accounts/origins/update-accounts-origins'; +import list_accounts_origins from './accounts/origins/list-accounts-origins'; +import delete_accounts_origins from './accounts/origins/delete-accounts-origins'; +import get_accounts_origins from './accounts/origins/get-accounts-origins'; +import create_accounts_url_endpoints from './accounts/url-endpoints/create-accounts-url-endpoints'; +import update_accounts_url_endpoints from './accounts/url-endpoints/update-accounts-url-endpoints'; +import list_accounts_url_endpoints from './accounts/url-endpoints/list-accounts-url-endpoints'; +import delete_accounts_url_endpoints from './accounts/url-endpoints/delete-accounts-url-endpoints'; +import get_accounts_url_endpoints from './accounts/url-endpoints/get-accounts-url-endpoints'; +import upload_v2_beta_files from './beta/v2/files/upload-v2-beta-files'; + +export const endpoints: Endpoint[] = []; + +function addEndpoint(endpoint: Endpoint) { + endpoints.push(endpoint); +} + +addEndpoint(create_custom_metadata_fields); +addEndpoint(update_custom_metadata_fields); +addEndpoint(list_custom_metadata_fields); +addEndpoint(delete_custom_metadata_fields); +addEndpoint(update_files); +addEndpoint(delete_files); +addEndpoint(copy_files); +addEndpoint(get_files); +addEndpoint(move_files); +addEndpoint(rename_files); +addEndpoint(upload_files); +addEndpoint(delete_files_bulk); +addEndpoint(add_tags_files_bulk); +addEndpoint(remove_ai_tags_files_bulk); +addEndpoint(remove_tags_files_bulk); +addEndpoint(list_files_versions); +addEndpoint(delete_files_versions); +addEndpoint(get_files_versions); +addEndpoint(restore_files_versions); +addEndpoint(get_files_metadata); +addEndpoint(get_from_url_files_metadata); +addEndpoint(list_assets); +addEndpoint(create_cache_invalidation); +addEndpoint(get_cache_invalidation); +addEndpoint(create_folders); +addEndpoint(delete_folders); +addEndpoint(copy_folders); +addEndpoint(move_folders); +addEndpoint(rename_folders); +addEndpoint(get_folders_job); +addEndpoint(get_accounts_usage); +addEndpoint(create_accounts_origins); +addEndpoint(update_accounts_origins); +addEndpoint(list_accounts_origins); +addEndpoint(delete_accounts_origins); +addEndpoint(get_accounts_origins); +addEndpoint(create_accounts_url_endpoints); +addEndpoint(update_accounts_url_endpoints); +addEndpoint(list_accounts_url_endpoints); +addEndpoint(delete_accounts_url_endpoints); +addEndpoint(get_accounts_url_endpoints); +addEndpoint(upload_v2_beta_files); + +export type Filter = { + type: 'resource' | 'operation' | 'tag' | 'tool'; + op: 'include' | 'exclude'; + value: string; +}; + +export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { + const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); + const unmatchedFilters = new Set(filters); + + const filtered = endpoints.filter((endpoint: Endpoint) => { + let included = false || allExcludes; + + for (const filter of filters) { + if (match(filter, endpoint)) { + unmatchedFilters.delete(filter); + included = filter.op === 'include'; + } + } + + return included; + }); + + // Check if any filters didn't match + const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); + if (unmatched.length > 0) { + throw new Error( + `The following filters did not match any endpoints: ${unmatched + .map((f) => `${f.type}=${f.value}`) + .join(', ')}`, + ); + } + + return filtered; +} + +function match({ type, value }: Filter, endpoint: Endpoint): boolean { + switch (type) { + case 'resource': { + const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; + const regex = new RegExp(regexStr); + return regex.test(normalizeResource(endpoint.metadata.resource)); + } + case 'operation': + return endpoint.metadata.operation === value; + case 'tag': + return endpoint.metadata.tags.includes(value); + case 'tool': + return endpoint.tool.name === value; + } +} + +function normalizeResource(resource: string): string { + return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); +} diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts new file mode 100644 index 00000000..8106d499 --- /dev/null +++ b/packages/mcp-server/src/tools/types.ts @@ -0,0 +1,103 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import ImageKit from '@imagekit/nodejs'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +type TextContentBlock = { + type: 'text'; + text: string; +}; + +type ImageContentBlock = { + type: 'image'; + data: string; + mimeType: string; +}; + +type AudioContentBlock = { + type: 'audio'; + data: string; + mimeType: string; +}; + +type ResourceContentBlock = { + type: 'resource'; + resource: + | { + uri: string; + mimeType: string; + text: string; + } + | { + uri: string; + mimeType: string; + blob: string; + }; +}; + +export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock; + +export type ToolCallResult = { + content: ContentBlock[]; + isError?: boolean; +}; + +export type HandlerFunction = ( + client: ImageKit, + args: Record | undefined, +) => Promise; + +export function asTextContentResult(result: unknown): ToolCallResult { + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +} + +export async function asBinaryContentResult(response: Response): Promise { + const blob = await response.blob(); + const mimeType = blob.type; + const data = Buffer.from(await blob.arrayBuffer()).toString('base64'); + if (mimeType.startsWith('image/')) { + return { + content: [{ type: 'image', mimeType, data }], + }; + } else if (mimeType.startsWith('audio/')) { + return { + content: [{ type: 'audio', mimeType, data }], + }; + } else { + return { + content: [ + { + type: 'resource', + resource: { + // We must give a URI, even though this isn't actually an MCP resource. + uri: 'resource://tool-response', + mimeType, + blob: data, + }, + }, + ], + }; + } +} + +export type Metadata = { + resource: string; + operation: 'read' | 'write'; + tags: string[]; + httpMethod?: string; + httpPath?: string; + operationId?: string; +}; + +export type Endpoint = { + metadata: Metadata; + tool: Tool; + handler: HandlerFunction; +}; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts new file mode 100644 index 00000000..d6272f6c --- /dev/null +++ b/packages/mcp-server/tests/compat.test.ts @@ -0,0 +1,1166 @@ +import { + truncateToolNames, + removeTopLevelUnions, + removeAnyOf, + inlineRefs, + applyCompatibilityTransformations, + removeFormats, + findUsedDefs, +} from '../src/compat'; +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { JSONSchema } from '../src/compat'; +import { Endpoint } from '../src/tools'; + +describe('truncateToolNames', () => { + it('should return original names when maxLength is 0 or negative', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 0)).toEqual(new Map()); + expect(truncateToolNames(names, -1)).toEqual(new Map()); + }); + + it('should return original names when all names are shorter than maxLength', () => { + const names = ['tool1', 'tool2', 'tool3']; + expect(truncateToolNames(names, 10)).toEqual(new Map()); + }); + + it('should truncate names longer than maxLength', () => { + const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; + expect(truncateToolNames(names, 10)).toEqual( + new Map([ + ['very-long-tool-name', 'very-long-'], + ['another-long-tool-name', 'another-lo'], + ]), + ); + }); + + it('should handle duplicate truncated names by appending numbers', () => { + const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; + expect(truncateToolNames(names, 8)).toEqual( + new Map([ + ['tool-name-a', 'tool-na1'], + ['tool-name-b', 'tool-na2'], + ['tool-name-c', 'tool-na3'], + ]), + ); + }); +}); + +describe('removeTopLevelUnions', () => { + const createTestTool = (overrides = {}): Tool => ({ + name: 'test-tool', + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + it('should return the original tool if it has no anyOf at the top level', () => { + const tool = createTestTool({ + inputSchema: { + type: 'object', + properties: { + foo: { type: 'string' }, + }, + }, + }); + + expect(removeTopLevelUnions(tool)).toEqual([tool]); + }); + + it('should split a tool with top-level anyOf into multiple tools', () => { + const tool = createTestTool({ + name: 'union-tool', + description: 'A tool with unions', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + description: 'Its the first variant', + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'union-tool_first_variant', + description: 'Its the first variant', + inputSchema: { + type: 'object', + title: 'first variant', + description: 'Its the first variant', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + }, + { + name: 'union-tool_second_variant', + description: 'A tool with unions', + inputSchema: { + type: 'object', + title: 'second variant', + description: 'A tool with unions', + properties: { + common: { type: 'string' }, + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + }, + ]); + }); + + it('should handle $defs and only include those used by the variant', () => { + const tool = createTestTool({ + name: 'defs-tool', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + def2: { type: 'number', minimum: 0 }, + unused: { type: 'boolean' }, + }, + anyOf: [ + { + properties: { + email: { $ref: '#/$defs/def1' }, + }, + }, + { + properties: { + count: { $ref: '#/$defs/def2' }, + }, + }, + ], + }, + }); + + const result = removeTopLevelUnions(tool); + + expect(result).toEqual([ + { + name: 'defs-tool_variant1', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + email: { $ref: '#/$defs/def1' }, + }, + $defs: { + def1: { type: 'string', format: 'email' }, + }, + }, + }, + { + name: 'defs-tool_variant2', + description: 'A tool with $defs', + inputSchema: { + type: 'object', + description: 'A tool with $defs', + properties: { + common: { type: 'string' }, + count: { $ref: '#/$defs/def2' }, + }, + $defs: { + def2: { type: 'number', minimum: 0 }, + }, + }, + }, + ]); + }); +}); + +describe('removeAnyOf', () => { + it('should return original schema if it has no anyOf', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { type: 'number' }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(schema); + }); + + it('should remove anyOf field and use the first variant', () => { + const schema = { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + required: ['variant1'], + }, + { + properties: { + variant2: { type: 'number' }, + }, + required: ['variant2'], + }, + ], + }; + + const expected = { + type: 'object', + properties: { + common: { type: 'string' }, + variant1: { type: 'string' }, + }, + required: ['variant1'], + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should recursively remove anyOf fields from nested properties', () => { + const schema = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + }, + anyOf: [ + { + properties: { + option1: { type: 'boolean' }, + }, + }, + { + properties: { + option2: { type: 'array' }, + }, + }, + ], + }, + }, + }; + + const expected = { + type: 'object', + properties: { + foo: { type: 'string' }, + nested: { + type: 'object', + properties: { + bar: { type: 'number' }, + option1: { type: 'boolean' }, + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); + + it('should handle arrays', () => { + const schema = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }; + + expect(removeAnyOf(schema)).toEqual(expected); + }); +}); + +describe('findUsedDefs', () => { + it('should handle circular references without stack overflow', () => { + const defs = { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/person' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('person'); + }).not.toThrow(); + }); + + it('should handle indirect circular references without stack overflow', () => { + const defs = { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Indirect circular reference + }, + }, + }; + + const schema = { + type: 'object', + properties: { + root: { $ref: '#/$defs/node' }, + }, + }; + + // This should not throw a stack overflow error + expect(() => { + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('node'); + expect(result).toHaveProperty('childNode'); + }).not.toThrow(); + }); + + it('should find all used definitions in non-circular schemas', () => { + const defs = { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { $ref: '#/$defs/address' }, + }, + }, + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + }, + unused: { + type: 'object', + properties: { + data: { type: 'string' }, + }, + }, + }; + + const schema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/user' }, + }, + }; + + const result = findUsedDefs(schema, defs); + expect(result).toHaveProperty('user'); + expect(result).toHaveProperty('address'); + expect(result).not.toHaveProperty('unused'); + }); +}); + +describe('inlineRefs', () => { + it('should return the original schema if it does not contain $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + }; + + expect(inlineRefs(schema)).toEqual(schema); + }); + + it('should inline simple $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should inline nested $refs', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + order: { $ref: '#/$defs/order' }, + }, + $defs: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { type: 'array', items: { $ref: '#/$defs/item' } }, + }, + }, + item: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + order: { + type: 'object', + properties: { + id: { type: 'string' }, + items: { + type: 'array', + items: { + type: 'object', + properties: { + product: { type: 'string' }, + quantity: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle circular references by removing the circular part', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + person: { $ref: '#/$defs/person' }, + }, + $defs: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + friend: { $ref: '#/$defs/person' }, // Circular reference + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + person: { + type: 'object', + properties: { + name: { type: 'string' }, + // friend property is removed to break the circular reference + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should handle indirect circular references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + node: { $ref: '#/$defs/node' }, + }, + $defs: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { $ref: '#/$defs/childNode' }, + }, + }, + childNode: { + type: 'object', + properties: { + value: { type: 'string' }, + parent: { $ref: '#/$defs/node' }, // Circular reference through childNode + }, + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + node: { + type: 'object', + properties: { + value: { type: 'string' }, + child: { + type: 'object', + properties: { + value: { type: 'string' }, + // parent property is removed to break the circular reference + }, + }, + }, + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); + + it('should preserve other properties when inlining references', () => { + const schema: JSONSchema = { + type: 'object', + properties: { + address: { $ref: '#/$defs/address', description: 'User address' }, + }, + $defs: { + address: { + type: 'object', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + const expected: JSONSchema = { + type: 'object', + properties: { + address: { + type: 'object', + description: 'User address', + properties: { + street: { type: 'string' }, + city: { type: 'string' }, + }, + required: ['street'], + }, + }, + }; + + expect(inlineRefs(schema)).toEqual(expected); + }); +}); + +describe('removeFormats', () => { + it('should return original schema if formats capability is true', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + expect(removeFormats(schema, true)).toEqual(schema); + }); + + it('should move format to description when formats capability is false', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + email: { type: 'string', description: 'An email field', format: 'email' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + email: { type: 'string', description: 'An email field (format: "email")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle properties without description', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', format: 'date' }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: '(format: "date")' }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle nested properties', () => { + const schema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle arrays of objects', () => { + const schema = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date', format: 'date' }, + end: { type: 'string', description: 'End date', format: 'date' }, + }, + }, + }, + }, + }; + + const expected = { + type: 'object', + properties: { + dates: { + type: 'array', + items: { + type: 'object', + properties: { + start: { type: 'string', description: 'Start date (format: "date")' }, + end: { type: 'string', description: 'End date (format: "date")' }, + }, + }, + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); + + it('should handle schemas with $defs', () => { + const schema = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field', format: 'date' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field', + format: 'date-time', + }, + }, + }; + + const expected = { + type: 'object', + properties: { + date: { type: 'string', description: 'A date field (format: "date")' }, + }, + $defs: { + timestamp: { + type: 'string', + description: 'A timestamp field (format: "date-time")', + }, + }, + }; + + expect(removeFormats(schema, false)).toEqual(expected); + }); +}); + +describe('applyCompatibilityTransformations', () => { + const createTestTool = (name: string, overrides = {}): Tool => ({ + name, + description: 'Test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + ...overrides, + }); + + const createTestEndpoint = (tool: Tool): Endpoint => ({ + tool, + handler: jest.fn(), + metadata: { + resource: 'test', + operation: 'read' as const, + tags: [], + }, + }); + + it('should not modify endpoints when all capabilities are enabled', () => { + const tool = createTestTool('test-tool'); + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed).toEqual(endpoints); + }); + + it('should split tools with top-level unions when topLevelUnions is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); + expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); + }); + + it('should handle variants without titles in removeTopLevelUnions', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + properties: { + variant1: { type: 'string' }, + }, + }, + { + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); + expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); + }); + + it('should truncate tool names when toolNameLength is set', () => { + const tools = [ + createTestTool('very-long-tool-name-that-exceeds-limit'), + createTestTool('another-long-tool-name-to-truncate'), + createTestTool('short-name'), + ]; + + const endpoints = tools.map(createTestEndpoint); + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); + expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); + expect(transformed[2]!.tool.name).toBe('short-name'); + }); + + it('should inline refs when refs capability is disabled', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + expect(schema.$defs).toBeUndefined(); + + if (schema.properties) { + expect(schema.properties['user']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + email: { type: 'string' }, + }, + }); + } + }); + + it('should preserve external refs when inlining', () => { + const tool = createTestTool('ref-tool', { + inputSchema: { + type: 'object', + properties: { + internal: { $ref: '#/$defs/internal' }, + external: { $ref: 'https://example.com/schemas/external.json' }, + }, + $defs: { + internal: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: true, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties) { + expect(schema.properties['internal']).toEqual({ + type: 'object', + properties: { + name: { type: 'string' }, + }, + }); + expect(schema.properties['external']).toEqual({ + $ref: 'https://example.com/schemas/external.json', + }); + } + }); + + it('should remove anyOf fields when unions capability is disabled', () => { + const tool = createTestTool('union-tool', { + inputSchema: { + type: 'object', + properties: { + field: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['field']) { + const field = schema.properties['field']; + expect(field.anyOf).toBeUndefined(); + expect(field.type).toBe('string'); + } + }); + + it('should correctly combine topLevelUnions and toolNameLength transformations', () => { + const tool = createTestTool('very-long-union-tool-name', { + inputSchema: { + type: 'object', + properties: { + common: { type: 'string' }, + }, + anyOf: [ + { + title: 'first variant', + properties: { + variant1: { type: 'string' }, + }, + }, + { + title: 'second variant', + properties: { + variant2: { type: 'number' }, + }, + }, + ], + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: false, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 20, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + expect(transformed.length).toBe(2); + + // Both names should be truncated because they exceed 20 characters + expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); + expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); + }); + + it('should correctly combine refs and unions transformations', () => { + const tool = createTestTool('complex-tool', { + inputSchema: { + type: 'object', + properties: { + user: { $ref: '#/$defs/user' }, + }, + $defs: { + user: { + type: 'object', + properties: { + preference: { + anyOf: [{ type: 'string' }, { type: 'number' }], + }, + }, + }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: false, + unions: false, + formats: true, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + // Refs should be inlined + expect(schema.$defs).toBeUndefined(); + + // Safely access nested properties + if (schema.properties && schema.properties['user']) { + const user = schema.properties['user']; + // User should be inlined + expect(user.type).toBe('object'); + + // AnyOf in the inlined user.preference should be removed + if (user.properties && user.properties['preference']) { + const preference = user.properties['preference']; + expect(preference.anyOf).toBeUndefined(); + expect(preference.type).toBe('string'); + } + } + }); + + it('should handle formats capability being false', () => { + const tool = createTestTool('format-tool', { + inputSchema: { + type: 'object', + properties: { + date: { type: 'string', description: 'A date', format: 'date' }, + }, + }, + }); + + const endpoints = [createTestEndpoint(tool)]; + + const capabilities = { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: false, + toolNameLength: undefined, + }; + + const transformed = applyCompatibilityTransformations(endpoints, capabilities); + const schema = transformed[0]!.tool.inputSchema as JSONSchema; + + if (schema.properties && schema.properties['date']) { + const dateField = schema.properties['date']; + expect(dateField['format']).toBeUndefined(); + expect(dateField['description']).toBe('A date (format: "date")'); + } + }); +}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts new file mode 100644 index 00000000..08963af8 --- /dev/null +++ b/packages/mcp-server/tests/dynamic-tools.test.ts @@ -0,0 +1,185 @@ +import { dynamicTools } from '../src/dynamic-tools'; +import { Endpoint } from '../src/tools'; + +describe('dynamicTools', () => { + const fakeClient = {} as any; + + const endpoints: Endpoint[] = [ + makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), + makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), + makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), + makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), + ]; + + const tools = dynamicTools(endpoints); + + const toolsMap = { + list_api_endpoints: toolOrError('list_api_endpoints'), + get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), + invoke_api_endpoint: toolOrError('invoke_api_endpoint'), + }; + + describe('list_api_endpoints', () => { + it('should return all endpoints when no search query is provided', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(endpoints.length); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); + expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); + }); + + it('should filter endpoints by name', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + + it('should filter endpoints by resource', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); + }); + + it('should filter endpoints by tag', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); + }); + + it('should be case insensitive in search', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools.length).toBe(2); + result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { + expect( + tool.name.toLowerCase().includes('admin') || + tool.resource.toLowerCase().includes('admin') || + tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), + ).toBeTruthy(); + }); + }); + + it('should filter endpoints by description', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'Test endpoint for user_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); + }); + + it('should filter endpoints by partial description match', async () => { + const content = await toolsMap.list_api_endpoints.handler(fakeClient, { + search_query: 'endpoint for user', + }); + const result = JSON.parse(content.content[0].text); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('user_endpoint'); + }); + }); + + describe('get_api_endpoint_schema', () => { + it('should return schema for existing endpoint', async () => { + const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { + endpoint: 'test_read_endpoint', + }); + const result = JSON.parse(content.content[0].text); + + expect(result).toEqual(endpoints[0]?.tool); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), + ).rejects.toThrow('Endpoint non_existent_endpoint not found'); + }); + + it('should throw error when no endpoint provided', async () => { + await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + }); + + describe('invoke_api_endpoint', () => { + it('should successfully invoke endpoint with valid arguments', async () => { + const mockHandler = endpoints[0]?.handler as jest.Mock; + mockHandler.mockClear(); + + await toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { testParam: 'test value' }, + }); + + expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); + }); + + it('should throw error for non-existent endpoint', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'non_existent_endpoint', + args: { testParam: 'test value' }, + }), + ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); + }); + + it('should throw error when no arguments provided', async () => { + await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( + 'No endpoint provided', + ); + }); + + it('should throw error for invalid argument schema', async () => { + await expect( + toolsMap.invoke_api_endpoint.handler(fakeClient, { + endpoint_name: 'test_read_endpoint', + args: { wrongParam: 'test value' }, // Missing required testParam + }), + ).rejects.toThrow(/Invalid arguments for endpoint/); + }); + }); + + function toolOrError(name: string) { + const tool = tools.find((tool) => tool.tool.name === name); + if (!tool) throw new Error(`Tool ${name} not found`); + return tool; + } +}); + +function makeEndpoint( + name: string, + resource: string, + operation: 'read' | 'write', + tags: string[] = [], +): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { + name, + description: `Test endpoint for ${name}`, + inputSchema: { + type: 'object', + properties: { + testParam: { type: 'string' }, + }, + required: ['testParam'], + }, + }, + handler: jest.fn().mockResolvedValue({ success: true }), + }; +} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts new file mode 100644 index 00000000..a8a5b81a --- /dev/null +++ b/packages/mcp-server/tests/options.test.ts @@ -0,0 +1,518 @@ +import { parseCLIOptions, parseQueryOptions } from '../src/options'; +import { Filter } from '../src/tools'; +import { parseEmbeddedJSON } from '../src/compat'; + +// Mock process.argv +const mockArgv = (args: string[]) => { + const originalArgv = process.argv; + process.argv = ['node', 'test.js', ...args]; + return () => { + process.argv = originalArgv; + }; +}; + +describe('parseCLIOptions', () => { + it('should parse basic filter options', () => { + const cleanup = mockArgv([ + '--tool=test-tool', + '--resource=test-resource', + '--operation=read', + '--tag=test-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + { type: 'operation', op: 'include', value: 'read' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + expect(result.list).toBe(false); + + cleanup(); + }); + + it('should parse exclusion filters', () => { + const cleanup = mockArgv([ + '--no-tool=exclude-tool', + '--no-resource=exclude-resource', + '--no-operation=write', + '--no-tag=exclude-tag', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({}); + + cleanup(); + }); + + it('should parse client presets', () => { + const cleanup = mockArgv(['--client=openai-agents']); + + const result = parseCLIOptions(); + + expect(result.client).toEqual('openai-agents'); + + cleanup(); + }); + + it('should parse individual capabilities', () => { + const cleanup = mockArgv([ + '--capability=top-level-unions', + '--capability=valid-json', + '--capability=refs', + '--capability=unions', + '--capability=tool-name-length=40', + ]); + + const result = parseCLIOptions(); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + toolNameLength: 40, + }); + + cleanup(); + }); + + it('should handle list option', () => { + const cleanup = mockArgv(['--list']); + + const result = parseCLIOptions(); + + expect(result.list).toBe(true); + + cleanup(); + }); + + it('should handle multiple filters of the same type', () => { + const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + cleanup(); + }); + + it('should handle comma-separated values in array options', () => { + const cleanup = mockArgv([ + '--tool=tool1,tool2', + '--resource=res1,res2', + '--capability=top-level-unions,valid-json,unions', + ]); + + const result = parseCLIOptions(); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ] as Filter[]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + unions: true, + }); + + cleanup(); + }); + + it('should handle invalid tool-name-length format', () => { + const cleanup = mockArgv(['--capability=tool-name-length=invalid']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); + + it('should handle unknown capability', () => { + const cleanup = mockArgv(['--capability=unknown-capability']); + + // Mock console.error to prevent output during test + const originalError = console.error; + console.error = jest.fn(); + + expect(() => parseCLIOptions()).toThrow(); + + console.error = originalError; + cleanup(); + }); +}); + +describe('parseQueryOptions', () => { + const defaultOptions = { + client: undefined, + includeDynamicTools: undefined, + includeAllTools: undefined, + filters: [], + capabilities: { + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }, + }; + + it('should parse basic filter options from query string', () => { + const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'test-resource' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'test-tag' }, + { type: 'tool', op: 'include', value: 'test-tool' }, + ]); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: undefined, + }); + }); + + it('should parse exclusion filters from query string', () => { + const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'exclude', value: 'exclude-resource' }, + { type: 'operation', op: 'exclude', value: 'write' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); + + it('should parse client option from query string', () => { + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should parse client capabilities from query string', () => { + const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: true, + refs: true, + unions: true, + formats: true, + toolNameLength: 40, + }); + }); + + it('should parse no-capability options from query string', () => { + const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: false, + validJson: true, + refs: false, + unions: true, + formats: false, + toolNameLength: undefined, + }); + }); + + it('should parse tools options from query string', () => { + const query = 'tools=dynamic&tools=all'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(true); + expect(result.includeAllTools).toBe(true); + }); + + it('should parse no-tools options from query string', () => { + const query = 'tools=dynamic&tools=all&no_tools=dynamic'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeDynamicTools).toBe(false); + expect(result.includeAllTools).toBe(true); + }); + + it('should handle array values in query string', () => { + const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'res1' }, + { type: 'resource', op: 'include', value: 'res2' }, + { type: 'tool', op: 'include', value: 'tool1' }, + { type: 'tool', op: 'include', value: 'tool2' }, + ]); + }); + + it('should merge with default options', () => { + const defaultWithFilters = { + ...defaultOptions, + filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], + client: 'cursor' as const, + includeDynamicTools: true, + }; + + const query = 'tool=new-tool&resource=new-resource'; + const result = parseQueryOptions(defaultWithFilters, query); + + expect(result.filters).toEqual([ + { type: 'tag', op: 'include', value: 'existing-tag' }, + { type: 'resource', op: 'include', value: 'new-resource' }, + { type: 'tool', op: 'include', value: 'new-tool' }, + ]); + + expect(result.client).toBe('cursor'); + expect(result.includeDynamicTools).toBe(true); + }); + + it('should override client from default options', () => { + const defaultWithClient = { + ...defaultOptions, + client: 'cursor' as const, + }; + + const query = 'client=openai-agents'; + const result = parseQueryOptions(defaultWithClient, query); + + expect(result.client).toBe('openai-agents'); + }); + + it('should merge capabilities with default options', () => { + const defaultWithCapabilities = { + ...defaultOptions, + capabilities: { + topLevelUnions: false, + validJson: false, + refs: true, + unions: true, + formats: true, + toolNameLength: 30, + }, + }; + + const query = 'capability=top-level-unions&no_capability=refs'; + const result = parseQueryOptions(defaultWithCapabilities, query); + + expect(result.capabilities).toEqual({ + topLevelUnions: true, + validJson: false, + refs: false, + unions: true, + formats: true, + toolNameLength: 30, + }); + }); + + it('should handle empty query string', () => { + const query = ''; + const result = parseQueryOptions(defaultOptions, query); + + expect(result).toEqual(defaultOptions); + }); + + it('should handle invalid query string gracefully', () => { + const query = 'invalid=value&operation=invalid-operation'; + + // Should throw due to Zod validation for invalid operation + expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); + }); + + it('should preserve default undefined values when not specified', () => { + const defaultWithUndefined = { + ...defaultOptions, + client: undefined, + includeDynamicTools: undefined, + includeAllTools: undefined, + }; + + const query = 'tool=test-tool'; + const result = parseQueryOptions(defaultWithUndefined, query); + + expect(result.client).toBeUndefined(); + expect(result.includeDynamicTools).toBeFalsy(); + expect(result.includeAllTools).toBeFalsy(); + }); + + it('should handle complex query with mixed include and exclude filters', () => { + const query = + 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.filters).toEqual([ + { type: 'resource', op: 'include', value: 'include-res' }, + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'include-tag' }, + { type: 'tool', op: 'include', value: 'include-tool' }, + { type: 'resource', op: 'exclude', value: 'exclude-res' }, + { type: 'tag', op: 'exclude', value: 'exclude-tag' }, + { type: 'tool', op: 'exclude', value: 'exclude-tool' }, + ]); + }); +}); + +describe('parseEmbeddedJSON', () => { + it('should not change non-string values', () => { + const args = { + numberProp: 42, + booleanProp: true, + objectProp: { nested: 'value' }, + arrayProp: [1, 2, 3], + nullProp: null, + undefinedProp: undefined, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberProp']).toBe(42); + expect(result['booleanProp']).toBe(true); + expect(result['objectProp']).toEqual({ nested: 'value' }); + expect(result['arrayProp']).toEqual([1, 2, 3]); + expect(result['nullProp']).toBe(null); + expect(result['undefinedProp']).toBe(undefined); + }); + + it('should parse valid JSON objects in string properties', () => { + const args = { + jsonObjectString: '{"key": "value", "number": 123}', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since changes were made + expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); + expect(result['regularString']).toBe('not json'); + }); + + it('should leave invalid JSON in string properties unchanged', () => { + const args = { + invalidJson1: '{"key": value}', // Missing quotes around value + invalidJson2: '{key: "value"}', // Missing quotes around key + invalidJson3: '{"key": "value",}', // Trailing comma + invalidJson4: 'just a regular string', + emptyString: '', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['invalidJson1']).toBe('{"key": value}'); + expect(result['invalidJson2']).toBe('{key: "value"}'); + expect(result['invalidJson3']).toBe('{"key": "value",}'); + expect(result['invalidJson4']).toBe('just a regular string'); + expect(result['emptyString']).toBe(''); + }); + + it('should not parse JSON primitives in string properties', () => { + const args = { + numberString: '123', + floatString: '45.67', + negativeNumberString: '-89', + booleanTrueString: 'true', + booleanFalseString: 'false', + nullString: 'null', + jsonArrayString: '[1, 2, 3, "test"]', + regularString: 'not json', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + expect(result['numberString']).toBe('123'); + expect(result['floatString']).toBe('45.67'); + expect(result['negativeNumberString']).toBe('-89'); + expect(result['booleanTrueString']).toBe('true'); + expect(result['booleanFalseString']).toBe('false'); + expect(result['nullString']).toBe('null'); + expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); + expect(result['regularString']).toBe('not json'); + }); + + it('should handle mixed valid objects and other JSON types', () => { + const args = { + validObject: '{"success": true}', + invalidObject: '{"missing": quote}', + validNumber: '42', + validArray: '[1, 2, 3]', + keepAsString: 'hello world', + nonString: 123, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).not.toBe(args); // Should return new object since some changes were made + expect(result['validObject']).toEqual({ success: true }); + expect(result['invalidObject']).toBe('{"missing": quote}'); + expect(result['validNumber']).toBe('42'); // Not parsed, remains string + expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string + expect(result['keepAsString']).toBe('hello world'); + expect(result['nonString']).toBe(123); + }); + + it('should return original object when no strings are present', () => { + const args = { + number: 42, + boolean: true, + object: { key: 'value' }, + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); + + it('should return original object when all strings are invalid JSON', () => { + const args = { + string1: 'hello', + string2: 'world', + string3: 'not json at all', + }; + const schema = {}; + + const result = parseEmbeddedJSON(args, schema); + + expect(result).toBe(args); // Should return original object since no changes made + }); +}); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts new file mode 100644 index 00000000..cfff24a2 --- /dev/null +++ b/packages/mcp-server/tests/tools.test.ts @@ -0,0 +1,225 @@ +import { Endpoint, Filter, Metadata, query } from '../src/tools'; + +describe('Endpoint filtering', () => { + const endpoints: Endpoint[] = [ + endpoint({ + resource: 'user', + operation: 'read', + tags: ['admin'], + toolName: 'retrieve_user', + }), + endpoint({ + resource: 'user.profile', + operation: 'write', + tags: [], + toolName: 'create_user_profile', + }), + endpoint({ + resource: 'user.profile', + operation: 'read', + tags: [], + toolName: 'get_user_profile', + }), + endpoint({ + resource: 'user.roles.permissions', + operation: 'write', + tags: ['admin', 'security'], + toolName: 'update_user_role_permissions', + }), + endpoint({ + resource: 'documents.metadata.tags', + operation: 'write', + tags: ['taxonomy', 'metadata'], + toolName: 'create_document_metadata_tags', + }), + endpoint({ + resource: 'organization.settings', + operation: 'read', + tags: ['admin', 'configuration'], + toolName: 'get_organization_settings', + }), + ]; + + const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ + { + name: 'match none', + filters: [], + expected: [], + }, + + // Resource tests + { + name: 'simple resource', + filters: [{ type: 'resource', op: 'include', value: 'user' }], + expected: ['retrieve_user'], + }, + { + name: 'exclude resource', + filters: [{ type: 'resource', op: 'exclude', value: 'user' }], + expected: [ + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'create_document_metadata_tags', + 'get_organization_settings', + ], + }, + { + name: 'resource and subresources', + filters: [{ type: 'resource', op: 'include', value: 'user*' }], + expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'just subresources', + filters: [{ type: 'resource', op: 'include', value: 'user.*' }], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + { + name: 'specific subresource', + filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], + expected: ['update_user_role_permissions'], + }, + { + name: 'deep wildcard match', + filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], + expected: ['create_document_metadata_tags'], + }, + + // Operation tests + { + name: 'read operation', + filters: [{ type: 'operation', op: 'include', value: 'read' }], + expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], + }, + { + name: 'write operation', + filters: [{ type: 'operation', op: 'include', value: 'write' }], + expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], + }, + { + name: 'resource and operation combined', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'operation', op: 'exclude', value: 'write' }, + ], + expected: ['get_user_profile'], + }, + + // Tag tests + { + name: 'admin tag', + filters: [{ type: 'tag', op: 'include', value: 'admin' }], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'taxonomy tag', + filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], + expected: ['create_document_metadata_tags'], + }, + { + name: 'multiple tags (OR logic)', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'include', value: 'security' }, + ], + expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], + }, + { + name: 'excluding a tag', + filters: [ + { type: 'tag', op: 'include', value: 'admin' }, + { type: 'tag', op: 'exclude', value: 'security' }, + ], + expected: ['retrieve_user', 'get_organization_settings'], + }, + + // Tool name tests + { + name: 'tool name match', + filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], + expected: ['get_organization_settings'], + }, + { + name: 'two tools match', + filters: [ + { type: 'tool', op: 'include', value: 'get_organization_settings' }, + { type: 'tool', op: 'include', value: 'create_user_profile' }, + ], + expected: ['create_user_profile', 'get_organization_settings'], + }, + { + name: 'excluding tool by name', + filters: [ + { type: 'resource', op: 'include', value: 'user*' }, + { type: 'tool', op: 'exclude', value: 'retrieve_user' }, + ], + expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], + }, + + // Complex combinations + { + name: 'complex filter: read operations with admin tag', + filters: [ + { type: 'operation', op: 'include', value: 'read' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + { + name: 'complex filter: user resources with no tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'exclude', value: 'admin' }, + ], + expected: ['create_user_profile', 'get_user_profile'], + }, + { + name: 'complex filter: user resources and tags', + filters: [ + { type: 'resource', op: 'include', value: 'user.profile' }, + { type: 'tag', op: 'include', value: 'admin' }, + ], + expected: [ + 'retrieve_user', + 'create_user_profile', + 'get_user_profile', + 'update_user_role_permissions', + 'get_organization_settings', + ], + }, + ]; + + tests.forEach((test) => { + it(`filters by ${test.name}`, () => { + const filtered = query(test.filters, endpoints); + expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); + }); + }); +}); + +function endpoint({ + resource, + operation, + tags, + toolName, +}: { + resource: string; + operation: Metadata['operation']; + tags: string[]; + toolName: string; +}): Endpoint { + return { + metadata: { + resource, + operation, + tags, + }, + tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, + handler: jest.fn(), + }; +} diff --git a/packages/mcp-server/tsc-multi.json b/packages/mcp-server/tsc-multi.json new file mode 100644 index 00000000..4facad5a --- /dev/null +++ b/packages/mcp-server/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + { "extname": ".js", "module": "commonjs" }, + { "extname": ".mjs", "module": "esnext" } + ], + "projects": ["tsconfig.build.json"] +} diff --git a/packages/mcp-server/tsconfig.build.json b/packages/mcp-server/tsconfig.build.json new file mode 100644 index 00000000..5111d3e2 --- /dev/null +++ b/packages/mcp-server/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "include": ["dist/src"], + "exclude": [], + "compilerOptions": { + "rootDir": "./dist/src", + "paths": { + "imagekit-api-mcp/*": ["dist/src/*"], + "imagekit-api-mcp": ["dist/src/index.ts"] + }, + "noEmit": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "pretty": true, + "sourceMap": true + } +} diff --git a/packages/mcp-server/tsconfig.dist-src.json b/packages/mcp-server/tsconfig.dist-src.json new file mode 100644 index 00000000..e9f2d70b --- /dev/null +++ b/packages/mcp-server/tsconfig.dist-src.json @@ -0,0 +1,11 @@ +{ + // this config is included in the published src directory to prevent TS errors + // from appearing when users go to source, and VSCode opens the source .ts file + // via declaration maps + "include": ["index.ts"], + "compilerOptions": { + "target": "es2015", + "lib": ["DOM"], + "moduleResolution": "node" + } +} diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json new file mode 100644 index 00000000..ddb25b3e --- /dev/null +++ b/packages/mcp-server/tsconfig.json @@ -0,0 +1,37 @@ +{ + "include": ["src", "tests", "examples"], + "exclude": [], + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "imagekit-api-mcp/*": ["src/*"], + "imagekit-api-mcp": ["src/index.ts"] + }, + "noEmit": true, + + "resolveJsonModule": true, + + "forceConsistentCasingInFileNames": true, + + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + + "skipLibCheck": true + } +} diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock new file mode 100644 index 00000000..ad819835 --- /dev/null +++ b/packages/mcp-server/yarn.lock @@ -0,0 +1,3916 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@anthropic-ai/dxt@^0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@anthropic-ai/dxt/-/dxt-0.2.6.tgz#636197c3d083c9136ac3b5a11d2ba82477fdc2c6" + integrity sha512-5VSqKRpkytTYh5UJz9jOaI8zLXNCe4Gc+ArKGFV6IeWnEPP0Qnd0k+V3pO8cYzp92Puf/+Cgo0xc4haE0azTXg== + dependencies: + "@inquirer/prompts" "^6.0.1" + commander "^13.1.0" + fflate "^0.8.2" + galactus "^1.0.0" + ignore "^7.0.5" + node-forge "^1.3.1" + pretty-bytes "^5.6.0" + zod "^3.25.67" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.2.tgz#4183f9e642fd84e74e3eea7ffa93a412e3b102c9" + integrity sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.1.tgz#89de51e86bd12246003e3524704c49541b16c3e6" + integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helpers" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.1", "@babel/generator@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.1.tgz#862d4fad858f7208edd487c28b58144036b76230" + integrity sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w== + dependencies: + "@babel/parser" "^7.27.1" + "@babel/types" "^7.27.1" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.1": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz#e1663b8b71d2de948da5c4fb2a20ca4f3ec27a6f" + integrity sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.1.tgz#ffc27013038607cdba3288e692c3611c06a18aa4" + integrity sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ== + dependencies: + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.1", "@babel/parser@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" + integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.1", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.1.tgz#4db772902b133bbddd1c4f7a7ee47761c1b9f291" + integrity sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.27.1" + "@babel/parser" "^7.27.1" + "@babel/template" "^7.27.1" + "@babel/types" "^7.27.1" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.3.3": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560" + integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cloudflare/cabidela@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@cloudflare/cabidela/-/cabidela-0.2.4.tgz#9a3e9212e636a24d796a8f16741c24885b326a1a" + integrity sha512-u/1OwwqfcMvjmUFOcb6QtFzVVGpncHJxwl254wjzp0JC5CUlBkV6r5BbRrHI5ZYJEAgu8NeeorirxngmMFPZjQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" + integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.13.tgz#ad0afd62baab1c23175115a9b62f511b6a751e45" + integrity sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw== + +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@modelcontextprotocol/sdk@^1.11.5": + version "1.17.3" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz#cf92354220f0183d28179e96a9bf3a8f6d3211ae" + integrity sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg== + dependencies: + ajv "^6.12.6" + content-type "^1.0.5" + cors "^2.8.5" + cross-spawn "^7.0.5" + eventsource "^3.0.2" + eventsource-parser "^3.0.0" + express "^5.0.1" + express-rate-limit "^7.5.0" + pkce-challenge "^5.0.0" + raw-body "^3.0.0" + zod "^3.23.8" + zod-to-json-schema "^3.24.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@ts-morph/common@~0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af" + integrity sha512-7uKjByfbPpwuzkstL3L5MQyuXPSKdoNG93Fmi2JoDcTf3pEP731JdRFAduRVkOs8oqxPsXKA+ScrWkdQ8t/I+Q== + dependencies: + fast-glob "^3.2.12" + minimatch "^7.4.3" + mkdirp "^2.1.6" + path-browserify "^1.0.1" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" + integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.19": + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^5.0.0": + version "5.0.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" + integrity sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" + integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.4.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "22.15.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" + integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== + dependencies: + undici-types "~6.21.0" + +"@types/node@^22.5.5": + version "22.18.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.0.tgz#9e4709be4f104e3568f7dd1c71e2949bf147a47b" + integrity sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ== + dependencies: + undici-types "~6.21.0" + +"@types/qs@*", "@types/qs@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" + integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" + integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/type-utils" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" + integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== + dependencies: + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" + integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + +"@typescript-eslint/type-utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" + integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== + dependencies: + "@typescript-eslint/typescript-estree" "8.31.1" + "@typescript-eslint/utils" "8.31.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" + integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== + +"@typescript-eslint/typescript-estree@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" + integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== + dependencies: + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/visitor-keys" "8.31.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" + integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.1" + "@typescript-eslint/types" "8.31.1" + "@typescript-eslint/typescript-estree" "8.31.1" + +"@typescript-eslint/visitor-keys@8.31.1": + version "8.31.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" + integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== + dependencies: + "@typescript-eslint/types" "8.31.1" + eslint-visitor-keys "^4.2.0" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@valtown/deno-http-worker@^0.0.21": + version "0.0.21" + resolved "https://registry.yarnpkg.com/@valtown/deno-http-worker/-/deno-http-worker-0.0.21.tgz#9ce3b5c1d0db211fe7ea8297881fe551838474ad" + integrity sha512-16kFuUykann75lNytnXXIQlmpzreZjzdyT27ebT3yNGCS3kKaS1iZYWHc3Si9An54Cphwr4qEcviChQkEeJBlA== + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.12.4, ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +body-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" + integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.6.3" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.24.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.5.tgz#aa0f5b8560fe81fde84c6dcb38f759bafba0e11b" + integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== + dependencies: + caniuse-lite "^1.0.30001716" + electron-to-chromium "^1.5.149" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.1.2, bytes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001716: + version "1.0.30001717" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" + integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +code-block-writer@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" + integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +depd@2.0.0, depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.149: + version "1.5.151" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz#5edd6c17e1b2f14b4662c41b9379f96cc8c2bb7c" + integrity sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-prettier@^5.0.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz#54d4748904e58eaf1ffe26c4bffa4986ca7f952b" + integrity sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.0" + +eslint-plugin-unused-imports@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz#63a98c9ad5f622cd9f830f70bc77739f25ccfe0d" + integrity sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^8.49.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventsource-parser@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.3.tgz#e9af1d40b77e6268cdcbc767321e8b9f066adea8" + integrity sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA== + +eventsource-parser@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" + integrity sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA== + +eventsource@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-3.0.7.tgz#1157622e2f5377bb6aef2114372728ba0c156989" + integrity sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA== + dependencies: + eventsource-parser "^3.0.1" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express-rate-limit@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz#6a67990a724b4fbbc69119419feef50c51e8b28f" + integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== + +express@^5.0.1, express@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" + integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.0" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.12, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +flora-colossus@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" + integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== + dependencies: + debug "^4.3.4" + fs-extra "^10.1.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +galactus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" + integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== + dependencies: + debug "^4.3.4" + flora-colossus "^2.0.0" + fs-extra "^10.1.0" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@2.0.0, http-errors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.4.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": + version "0.8.6" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^7.4.3: + version "7.4.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" + integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-all@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" + integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== + dependencies: + p-map "^4.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkce-challenge@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz#c3a405cb49e272094a38e890a2b51da0228c4d97" + integrity sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.0.0: + version "3.5.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + dependencies: + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1, statuses@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-to-stream@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" + integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== + dependencies: + readable-stream "^3.4.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +superstruct@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" + integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" + integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== + dependencies: + "@pkgr/core" "^0.2.3" + tslib "^2.8.1" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +ts-api-utils@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +ts-jest@^29.1.0: + version "29.3.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.2.tgz#0576cdf0a507f811fe73dcd16d135ce89f8156cb" + integrity sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.1" + type-fest "^4.39.1" + yargs-parser "^21.1.1" + +ts-morph@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-19.0.0.tgz#43e95fb0156c3fe3c77c814ac26b7d0be2f93169" + integrity sha512-D6qcpiJdn46tUqV45vr5UGM2dnIEuTGNxVhg0sk5NX11orcouwj6i1bMqZIz2mZTZB1Hcgy7C3oEVhAT+f6mbQ== + dependencies: + "@ts-morph/common" "~0.20.0" + code-block-writer "^12.0.0" + +ts-node@^10.5.0: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" + dependencies: + debug "^4.3.7" + fast-glob "^3.3.2" + get-stdin "^8.0.0" + p-all "^3.0.0" + picocolors "^1.1.1" + signal-exit "^3.0.7" + string-to-stream "^3.0.1" + superstruct "^1.0.4" + tslib "^2.8.1" + yargs "^17.7.2" + +tsconfig-paths@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.39.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.0, type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typescript@5.8.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +vary@^1, vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + 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" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + +zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: + version "3.24.5" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" + integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== + +zod-validation-error@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.1.tgz#a105723eb40299578a6a38cb86647068f6d005b1" + integrity sha512-F3rdaCOHs5ViJ5YTz5zzRtfkQdMdIeKudJAoxy7yB/2ZMEHw73lmCAcQw11r7++20MyGl4WV59EVh7A9rNAyog== + +zod@^3.23.8: + version "3.24.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" + integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== + +zod@^3.25.20, zod@^3.25.67: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/release-please-config.json b/release-please-config.json index 1ebd0bde..b1909804 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -60,5 +60,14 @@ } ], "release-type": "node", - "extra-files": ["src/version.ts", "README.md"] + "extra-files": [ + "src/version.ts", + "README.md", + "packages/mcp-server/yarn.lock", + { + "type": "json", + "path": "packages/mcp-server/package.json", + "jsonpath": "$.version" + } + ] } diff --git a/scripts/build b/scripts/build index 470e580f..fe159668 100755 --- a/scripts/build +++ b/scripts/build @@ -49,3 +49,9 @@ if [ -e ./scripts/build-deno ] then ./scripts/build-deno fi +# build all sub-packages +for dir in packages/*; do + if [ -d "$dir" ]; then + (cd "$dir" && yarn install && yarn build) + fi +done diff --git a/scripts/build-all b/scripts/build-all new file mode 100755 index 00000000..4e5ac01f --- /dev/null +++ b/scripts/build-all @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# build-all is deprecated, use build instead + +bash ./scripts/build diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 00000000..50e93fef --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,102 @@ +/** + * Called from the `create-releases.yml` workflow with the output + * of the release please action as the first argument. + * + * Example JSON input: + * + * ```json + { + "releases_created": "true", + "release_created": "true", + "id": "137967744", + "name": "sdk: v0.14.5", + "tag_name": "sdk-v0.14.5", + "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "body": "## 0.14.5 (2024-01-22)\n\n...", + "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5", + "draft": "false", + "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}", + "path": ".", + "version": "0.14.5", + "major": "0", + "minor": "14", + "patch": "5", + "packages/additional-sdk--release_created": "true", + "packages/additional-sdk--id": "137967756", + "packages/additional-sdk--name": "additional-sdk: v0.5.2", + "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2", + "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e", + "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...", + "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2", + "packages/additional-sdk--draft": "false", + "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}", + "packages/additional-sdk--path": "packages/additional-sdk", + "packages/additional-sdk--version": "0.5.2", + "packages/additional-sdk--major": "0", + "packages/additional-sdk--minor": "5", + "packages/additional-sdk--patch": "2", + "paths_released": "[\".\",\"packages/additional-sdk\"]" + } + ``` + */ + +import { execSync } from 'child_process'; +import path from 'path'; + +function main() { + const data = process.argv[2] ?? process.env['DATA']; + if (!data) { + throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`); + } + + const rootDir = path.join(__dirname, '..'); + console.log('root dir', rootDir); + console.log(`publish-packages called with ${data}`); + + const outputs = JSON.parse(data); + + const rawPaths = outputs.paths_released; + + if (!rawPaths) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs to contain a truthy `paths_released` property'); + } + if (typeof rawPaths !== 'string') { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be a JSON string'); + } + + const paths = JSON.parse(rawPaths); + if (!Array.isArray(paths)) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to be an array'); + } + if (!paths.length) { + console.error(JSON.stringify(outputs, null, 2)); + throw new Error('Expected outputs `paths_released` property to contain at least one entry'); + } + + const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm'); + console.log('Using publish script at', publishScriptPath); + + console.log('Ensuring root package is built'); + console.log(`$ yarn build`); + execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' }); + + for (const relPackagePath of paths) { + console.log('\n'); + + const packagePath = path.join(rootDir, relPackagePath); + console.log(`Publishing in directory: ${packagePath}`); + + console.log(`$ yarn install`); + execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + + console.log(`$ bash ${publishScriptPath}`); + execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' }); + } + + console.log('Finished publishing packages'); +} + +main(); diff --git a/scripts/utils/make-dist-package-json.cjs b/scripts/utils/make-dist-package-json.cjs index 7c24f56e..4d6634ea 100644 --- a/scripts/utils/make-dist-package-json.cjs +++ b/scripts/utils/make-dist-package-json.cjs @@ -12,6 +12,14 @@ processExportMap(pkgJson.exports); for (const key of ['types', 'main', 'module']) { if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); } +// Fix bin paths if present +if (pkgJson.bin) { + for (const key in pkgJson.bin) { + if (typeof pkgJson.bin[key] === 'string') { + pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './'); + } + } +} delete pkgJson.devDependencies; delete pkgJson.scripts.prepack; diff --git a/src/client.ts b/src/client.ts index 476df619..cb30dfa6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -88,22 +88,26 @@ import { isEmptyObj } from './internal/utils/values'; export interface ClientOptions { /** - * Your ImageKit private API key (it starts with `private_`). - * You can view and manage API keys in the [dashboard](https://imagekit.io/dashboard/developer/api-keys). + * Your ImageKit private API key (starts with `private_`). + * You can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys). * */ privateKey?: string | undefined; /** - * ImageKit Basic Auth only uses the `private_key` as username and ignores the password. + * Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password. + * The password field is automatically managed by the SDK and should not be set. * */ password?: string | null | undefined; /** - * Your ImageKit webhook secret. This is used by the SDK to verify webhook signatures. It starts with a `whsec_` prefix. - * You can view and manage your webhook secret in the [dashboard](https://imagekit.io/dashboard/developer/webhooks). - * Treat the secret like a password, keep it private and do not expose it publicly. + * Your ImageKit webhook secret used by the SDK to verify webhook signatures for security. + * This secret starts with a `whsec_` prefix and is essential for webhook verification. + * You can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks). + * + * **Security Note**: Treat this secret like a password - keep it private and never expose it publicly. + * This field is optional and only required if you plan to use webhook signature verification. * Learn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature). * */ From 608ef9945b576180c3786380262b1a4074bef456 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:52:09 +0000 Subject: [PATCH 59/73] feat(api): manual updates --- .stats.yml | 2 +- packages/mcp-server/cloudflare-worker/src/index.ts | 4 ++-- packages/mcp-server/manifest.json | 4 ++-- src/client.ts | 14 +++++--------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.stats.yml b/.stats.yml index d3fcabb4..ee45f3d5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: c9c7bed2a4341f915a2dc85958ce7f0e +config_hash: 51a9632be24fc533ad69a5bd56934651 diff --git a/packages/mcp-server/cloudflare-worker/src/index.ts b/packages/mcp-server/cloudflare-worker/src/index.ts index 7f7a1de9..75fcea3c 100644 --- a/packages/mcp-server/cloudflare-worker/src/index.ts +++ b/packages/mcp-server/cloudflare-worker/src/index.ts @@ -30,7 +30,7 @@ const serverConfig: ServerConfig = { key: 'password', label: 'Password', description: - 'Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password.\nThe password field is automatically managed by the SDK and should not be set.\n', + 'ImageKit uses your API key as username and ignores the password. \nThe SDK sets a dummy value. You can ignore this field.\n', required: false, default: 'do_not_set', placeholder: 'My Password', @@ -40,7 +40,7 @@ const serverConfig: ServerConfig = { key: 'webhookSecret', label: 'Webhook Secret', description: - 'Your ImageKit webhook secret used by the SDK to verify webhook signatures for security.\nThis secret starts with a `whsec_` prefix and is essential for webhook verification.\nYou can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\n\n**Security Note**: Treat this secret like a password - keep it private and never expose it publicly.\nThis field is optional and only required if you plan to use webhook signature verification.\nLearn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature).\n', + "Your ImageKit webhook secret for verifying webhook signatures (starts with `whsec_`).\nYou can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\nOnly required if you're using webhooks.\n", required: false, default: null, placeholder: 'My Webhook Secret', diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 37d0121d..8b7c24b7 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -35,13 +35,13 @@ }, "OPTIONAL_IMAGEKIT_IGNORES_THIS": { "title": "password", - "description": "Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password.\nThe password field is automatically managed by the SDK and should not be set.\n", + "description": "ImageKit uses your API key as username and ignores the password. \nThe SDK sets a dummy value. You can ignore this field.\n", "required": false, "type": "string" }, "IMAGEKIT_WEBHOOK_SECRET": { "title": "webhook_secret", - "description": "Your ImageKit webhook secret used by the SDK to verify webhook signatures for security.\nThis secret starts with a `whsec_` prefix and is essential for webhook verification.\nYou can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\n\n**Security Note**: Treat this secret like a password - keep it private and never expose it publicly.\nThis field is optional and only required if you plan to use webhook signature verification.\nLearn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature).\n", + "description": "Your ImageKit webhook secret for verifying webhook signatures (starts with `whsec_`).\nYou can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks).\nOnly required if you're using webhooks.\n", "required": false, "type": "string" } diff --git a/src/client.ts b/src/client.ts index cb30dfa6..972c9577 100644 --- a/src/client.ts +++ b/src/client.ts @@ -95,20 +95,16 @@ export interface ClientOptions { privateKey?: string | undefined; /** - * Leave this field unset. ImageKit uses Basic Authentication scheme that requires the `private_key` as the username and empty string as the password. - * The password field is automatically managed by the SDK and should not be set. + * ImageKit uses your API key as username and ignores the password. + * The SDK sets a dummy value. You can ignore this field. * */ password?: string | null | undefined; /** - * Your ImageKit webhook secret used by the SDK to verify webhook signatures for security. - * This secret starts with a `whsec_` prefix and is essential for webhook verification. - * You can view and manage your webhook secret in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks). - * - * **Security Note**: Treat this secret like a password - keep it private and never expose it publicly. - * This field is optional and only required if you plan to use webhook signature verification. - * Learn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature). + * Your ImageKit webhook secret for verifying webhook signatures (starts with `whsec_`). + * You can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/webhooks). + * Only required if you're using webhooks. * */ webhookSecret?: string | null | undefined; From 69968b160ccf3e4dbd68a6356714d75dd0d63acb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 02:38:30 +0000 Subject: [PATCH 60/73] chore: do not install brew dependencies in ./scripts/bootstrap by default --- scripts/bootstrap | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index 062a0349..a8b69ff3 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi From 70c98e08925b1884713e524129227003af75c7b6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 08:05:21 +0000 Subject: [PATCH 61/73] feat(api): Update env var name --- .stats.yml | 2 +- README.md | 4 ++-- packages/mcp-server/README.md | 6 +++--- packages/mcp-server/manifest.json | 4 ++-- packages/mcp-server/src/headers.ts | 6 +++--- src/client.ts | 6 +++--- tests/index.test.ts | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index ee45f3d5..0f9a4aa3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: 51a9632be24fc533ad69a5bd56934651 +config_hash: f1fafe5e607e996b58b67fd1dd3e74fa diff --git a/README.md b/README.md index 5f819a47..d4d929e0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The full API of this library can be found in [api.md](api.md). import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], // This is the default and can be omitted password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); @@ -47,7 +47,7 @@ This library includes TypeScript definitions for all request params and response import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], // This is the default and can be omitted + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], // This is the default and can be omitted password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 1ccd5dbd..54840253 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -19,7 +19,7 @@ cd imagekit-nodejs ```sh # set env vars as needed -export IMAGEKIT_PRIVATE_API_KEY="My Private Key" +export IMAGEKIT_PRIVATE_KEY="My Private Key" export OPTIONAL_IMAGEKIT_IGNORES_THIS="My Password" export IMAGEKIT_WEBHOOK_SECRET="My Webhook Secret" node ./packages/mcp-server/dist/index.js @@ -44,7 +44,7 @@ For clients with a configuration JSON, it might look something like this: "command": "node", "args": ["/path/to/local/imagekit-nodejs/packages/mcp-server", "--client=claude", "--tools=dynamic"], "env": { - "IMAGEKIT_PRIVATE_API_KEY": "My Private Key", + "IMAGEKIT_PRIVATE_KEY": "My Private Key", "OPTIONAL_IMAGEKIT_IGNORES_THIS": "My Password", "IMAGEKIT_WEBHOOK_SECRET": "My Webhook Secret" } @@ -154,7 +154,7 @@ Authorization can be provided via the `Authorization` header using the Basic sch Additionally, authorization can be provided via the following headers: | Header | Equivalent client option | Security scheme | | ---------------------------------- | ------------------------ | --------------- | -| `x-imagekit-private-api-key` | `privateKey` | basicAuth | +| `x-imagekit-private-key` | `privateKey` | basicAuth | | `x-optional-imagekit-ignores-this` | `password` | basicAuth | A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`: diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 8b7c24b7..d9546aa1 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -20,14 +20,14 @@ "command": "node", "args": ["${__dirname}/index.js"], "env": { - "IMAGEKIT_PRIVATE_API_KEY": "${user_config.IMAGEKIT_PRIVATE_API_KEY}", + "IMAGEKIT_PRIVATE_KEY": "${user_config.IMAGEKIT_PRIVATE_KEY}", "OPTIONAL_IMAGEKIT_IGNORES_THIS": "${user_config.OPTIONAL_IMAGEKIT_IGNORES_THIS}", "IMAGEKIT_WEBHOOK_SECRET": "${user_config.IMAGEKIT_WEBHOOK_SECRET}" } } }, "user_config": { - "IMAGEKIT_PRIVATE_API_KEY": { + "IMAGEKIT_PRIVATE_KEY": { "title": "private_key", "description": "Your ImageKit private API key (starts with `private_`).\nYou can find this in the [ImageKit dashboard](https://imagekit.io/dashboard/developer/api-keys).\n", "required": true, diff --git a/packages/mcp-server/src/headers.ts b/packages/mcp-server/src/headers.ts index fdd56f48..63e3abc9 100644 --- a/packages/mcp-server/src/headers.ts +++ b/packages/mcp-server/src/headers.ts @@ -20,9 +20,9 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial = } const privateKey = - Array.isArray(req.headers['x-imagekit-private-api-key']) ? - req.headers['x-imagekit-private-api-key'][0] - : req.headers['x-imagekit-private-api-key']; + Array.isArray(req.headers['x-imagekit-private-key']) ? + req.headers['x-imagekit-private-key'][0] + : req.headers['x-imagekit-private-key']; const password = Array.isArray(req.headers['x-optional-imagekit-ignores-this']) ? req.headers['x-optional-imagekit-ignores-this'][0] diff --git a/src/client.ts b/src/client.ts index 972c9577..1560e2bc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -201,7 +201,7 @@ export class ImageKit { /** * API Client for interfacing with the Image Kit API. * - * @param {string | undefined} [opts.privateKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined] + * @param {string | undefined} [opts.privateKey=process.env['IMAGEKIT_PRIVATE_KEY'] ?? undefined] * @param {string | null | undefined} [opts.password=process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] ?? do_not_set] * @param {string | null | undefined} [opts.webhookSecret=process.env['IMAGEKIT_WEBHOOK_SECRET'] ?? null] * @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API. @@ -214,14 +214,14 @@ export class ImageKit { */ constructor({ baseURL = readEnv('IMAGE_KIT_BASE_URL'), - privateKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'), + privateKey = readEnv('IMAGEKIT_PRIVATE_KEY'), password = readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS') ?? 'do_not_set', webhookSecret = readEnv('IMAGEKIT_WEBHOOK_SECRET') ?? null, ...opts }: ClientOptions = {}) { if (privateKey === undefined) { throw new Errors.ImageKitError( - "The IMAGEKIT_PRIVATE_API_KEY environment variable is missing or empty; either provide it, or instantiate the ImageKit client with an privateKey option, like new ImageKit({ privateKey: 'My Private Key' }).", + "The IMAGEKIT_PRIVATE_KEY environment variable is missing or empty; either provide it, or instantiate the ImageKit client with an privateKey option, like new ImageKit({ privateKey: 'My Private Key' }).", ); } diff --git a/tests/index.test.ts b/tests/index.test.ts index f3f1a3de..dd3a4f77 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -472,7 +472,7 @@ describe('instantiate client', () => { test('with environment variable arguments', () => { // set options via env var - process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'My Private Key'; + process.env['IMAGEKIT_PRIVATE_KEY'] = 'My Private Key'; process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'My Password'; const client = new ImageKit(); expect(client.privateKey).toBe('My Private Key'); @@ -481,7 +481,7 @@ describe('instantiate client', () => { test('with overridden environment variable arguments', () => { // set options via env var - process.env['IMAGEKIT_PRIVATE_API_KEY'] = 'another My Private Key'; + process.env['IMAGEKIT_PRIVATE_KEY'] = 'another My Private Key'; process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] = 'another My Password'; const client = new ImageKit({ privateKey: 'My Private Key', password: 'My Password' }); expect(client.privateKey).toBe('My Private Key'); From 2f93b891233782e8f6af350905a979f683173458 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sat, 20 Sep 2025 13:41:50 +0530 Subject: [PATCH 62/73] fix: update privateAPIKey to privateKey in code and tests --- README.md | 2 +- src/resources/helper.ts | 6 +++--- tests/helper-authentication.test.ts | 2 +- tests/url-generation/basic.test.ts | 2 +- tests/url-generation/buildTransformationString.test.ts | 2 +- tests/url-generation/overlay.test.ts | 2 +- tests/url-generation/signing.test.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d4d929e0..133a3b74 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Generate a simple URL without any transformations: import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: process.env['IMAGEKIT_PRIVATE_API_KEY'], + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], password: process.env['ORG_MY_PASSWORD_TOKEN'], }); diff --git a/src/resources/helper.ts b/src/resources/helper.ts index 4d48568d..f6e63547 100644 --- a/src/resources/helper.ts +++ b/src/resources/helper.ts @@ -98,7 +98,7 @@ export class Helper extends APIResource { const expiryTimestamp = getSignatureTimestamp(opts.expiresIn); const urlSignature = getSignature({ - privateKey: this._client.privateAPIKey, + privateKey: this._client.privateKey, url: finalUrl, urlEndpoint: opts.urlEndpoint, expiryTimestamp, @@ -151,7 +151,7 @@ export class Helper extends APIResource { * @throws {Error} If the private API key is not configured (should not happen in normal usage) */ getAuthenticationParameters(token?: string, expire?: number) { - if (!this._client.privateAPIKey) { + if (!this._client.privateKey) { throw new Error('Private API key is required for authentication parameters generation'); } @@ -161,7 +161,7 @@ export class Helper extends APIResource { const finalToken = token || uuid4(); const finalExpire = expire || defaultExpire; - return getAuthenticationParameters(finalToken, finalExpire, this._client.privateAPIKey); + return getAuthenticationParameters(finalToken, finalExpire, this._client.privateKey); } } diff --git a/tests/helper-authentication.test.ts b/tests/helper-authentication.test.ts index 2b40e317..41616193 100644 --- a/tests/helper-authentication.test.ts +++ b/tests/helper-authentication.test.ts @@ -1,7 +1,7 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateAPIKey: 'private_key_test', + privateKey: 'private_key_test', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/url-generation/basic.test.ts b/tests/url-generation/basic.test.ts index f22a1a12..85f59ab1 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/url-generation/basic.test.ts @@ -2,7 +2,7 @@ import ImageKit from '@imagekit/nodejs'; import type { SrcOptions } from '../../src/resources/shared'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private API Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/url-generation/buildTransformationString.test.ts b/tests/url-generation/buildTransformationString.test.ts index 44310fcd..31ae95a0 100644 --- a/tests/url-generation/buildTransformationString.test.ts +++ b/tests/url-generation/buildTransformationString.test.ts @@ -2,7 +2,7 @@ import ImageKit from '@imagekit/nodejs'; import type { Transformation } from '../../src/resources/shared'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private API Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/url-generation/overlay.test.ts b/tests/url-generation/overlay.test.ts index 7d43bd7d..3d36e37e 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/url-generation/overlay.test.ts @@ -2,7 +2,7 @@ import ImageKit from '@imagekit/nodejs'; import { safeBtoa } from '../../src/lib/transformation-utils'; const client = new ImageKit({ - privateAPIKey: 'My Private API Key', + privateKey: 'My Private API Key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/url-generation/signing.test.ts b/tests/url-generation/signing.test.ts index 38afeef0..dee86d63 100644 --- a/tests/url-generation/signing.test.ts +++ b/tests/url-generation/signing.test.ts @@ -8,7 +8,7 @@ import ImageKit from '@imagekit/nodejs'; * Ideally this code would not require any upkeeping. */ const client = new ImageKit({ - privateAPIKey: 'dummy-key', + privateKey: 'dummy-key', password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); From 83668ec0ec483214775296b9e8db0b393d455d9c Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sat, 20 Sep 2025 14:49:26 +0530 Subject: [PATCH 63/73] organize custom added test cases --- tests/{ => custom-tests}/helper-authentication.test.ts | 0 tests/{ => custom-tests}/url-generation/basic.test.ts | 2 +- .../url-generation/buildTransformationString.test.ts | 2 +- tests/{ => custom-tests}/url-generation/overlay.test.ts | 2 +- tests/{ => custom-tests}/url-generation/signing.test.ts | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename tests/{ => custom-tests}/helper-authentication.test.ts (100%) rename tests/{ => custom-tests}/url-generation/basic.test.ts (99%) rename tests/{ => custom-tests}/url-generation/buildTransformationString.test.ts (93%) rename tests/{ => custom-tests}/url-generation/overlay.test.ts (99%) rename tests/{ => custom-tests}/url-generation/signing.test.ts (100%) diff --git a/tests/helper-authentication.test.ts b/tests/custom-tests/helper-authentication.test.ts similarity index 100% rename from tests/helper-authentication.test.ts rename to tests/custom-tests/helper-authentication.test.ts diff --git a/tests/url-generation/basic.test.ts b/tests/custom-tests/url-generation/basic.test.ts similarity index 99% rename from tests/url-generation/basic.test.ts rename to tests/custom-tests/url-generation/basic.test.ts index 85f59ab1..ff996a9c 100644 --- a/tests/url-generation/basic.test.ts +++ b/tests/custom-tests/url-generation/basic.test.ts @@ -1,5 +1,5 @@ import ImageKit from '@imagekit/nodejs'; -import type { SrcOptions } from '../../src/resources/shared'; +import type { SrcOptions } from '../../../src/resources/shared'; const client = new ImageKit({ privateKey: 'My Private API Key', diff --git a/tests/url-generation/buildTransformationString.test.ts b/tests/custom-tests/url-generation/buildTransformationString.test.ts similarity index 93% rename from tests/url-generation/buildTransformationString.test.ts rename to tests/custom-tests/url-generation/buildTransformationString.test.ts index 31ae95a0..6efb6cdd 100644 --- a/tests/url-generation/buildTransformationString.test.ts +++ b/tests/custom-tests/url-generation/buildTransformationString.test.ts @@ -1,5 +1,5 @@ import ImageKit from '@imagekit/nodejs'; -import type { Transformation } from '../../src/resources/shared'; +import type { Transformation } from '../../../src/resources/shared'; const client = new ImageKit({ privateKey: 'My Private API Key', diff --git a/tests/url-generation/overlay.test.ts b/tests/custom-tests/url-generation/overlay.test.ts similarity index 99% rename from tests/url-generation/overlay.test.ts rename to tests/custom-tests/url-generation/overlay.test.ts index 3d36e37e..2fd3ced9 100644 --- a/tests/url-generation/overlay.test.ts +++ b/tests/custom-tests/url-generation/overlay.test.ts @@ -1,5 +1,5 @@ import ImageKit from '@imagekit/nodejs'; -import { safeBtoa } from '../../src/lib/transformation-utils'; +import { safeBtoa } from '../../../src/lib/transformation-utils'; const client = new ImageKit({ privateKey: 'My Private API Key', diff --git a/tests/url-generation/signing.test.ts b/tests/custom-tests/url-generation/signing.test.ts similarity index 100% rename from tests/url-generation/signing.test.ts rename to tests/custom-tests/url-generation/signing.test.ts From 34d2eb1c9de598e7f01156588a8f942dc36f8a70 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 09:24:00 +0000 Subject: [PATCH 64/73] feat(api): update api docs link --- .stats.yml | 2 +- README.md | 2 +- packages/mcp-server/manifest.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0f9a4aa3..7dfc3c2a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: f1fafe5e607e996b58b67fd1dd3e74fa +config_hash: 5f7498f5ea66e8a544c6c37b10f77467 diff --git a/README.md b/README.md index 133a3b74..8e7ad8af 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This library provides convenient access to the Image Kit REST API from server-side TypeScript or JavaScript. -The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs/api-reference). The full API of this library can be found in [api.md](api.md). It is generated with [Stainless](https://www.stainless.com/). diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index d9546aa1..7adb30f7 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -12,7 +12,7 @@ "url": "git+https://github.com/imagekit-developer/imagekit-nodejs.git" }, "homepage": "https://github.com/imagekit-developer/imagekit-nodejs/tree/main/packages/mcp-server#readme", - "documentation": "https://imagekit.io/docs", + "documentation": "https://imagekit.io/docs/api-reference", "server": { "type": "node", "entry_point": "index.js", From 454c7225ad3fbfda4f6807a0655b5d0b430b16d8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 09:25:20 +0000 Subject: [PATCH 65/73] feat(api): remove Stainless attribution from readme --- .stats.yml | 2 +- README.md | 2 -- packages/mcp-server/README.md | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7dfc3c2a..e1604c7a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 42 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d1a3e6dfc45ae832b6b14a0aef25878985c679fa9f48c1470df188b1578ba648.yml openapi_spec_hash: 1d382866fce3284f26d341f112988d9d -config_hash: 5f7498f5ea66e8a544c6c37b10f77467 +config_hash: ff23f46fe08ef3f43c57c8cf13eff3a1 diff --git a/README.md b/README.md index 8e7ad8af..ca1aebba 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ This library provides convenient access to the Image Kit REST API from server-si The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs/api-reference). The full API of this library can be found in [api.md](api.md). -It is generated with [Stainless](https://www.stainless.com/). - ## Installation ```sh diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 54840253..95170839 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -1,7 +1,5 @@ # Image Kit TypeScript MCP Server -It is generated with [Stainless](https://www.stainless.com/). - ## Installation ### Building From 08a5744777862dcb8b156ed47b31865db2c9f837 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sat, 20 Sep 2025 14:56:44 +0530 Subject: [PATCH 66/73] feat: remove password field from ImageKit client initialization in tests and documentation --- README.md | 5 +---- tests/custom-tests/helper-authentication.test.ts | 1 - tests/custom-tests/url-generation/basic.test.ts | 1 - .../url-generation/buildTransformationString.test.ts | 1 - tests/custom-tests/url-generation/overlay.test.ts | 1 - tests/custom-tests/url-generation/signing.test.ts | 1 - 6 files changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index ca1aebba..9e4b06a0 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], // This is the default and can be omitted - password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); const response = await client.files.upload({ @@ -46,7 +45,6 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], // This is the default and can be omitted - password: process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'], // This is the default and can be omitted }); const params: ImageKit.FileUploadParams = { @@ -99,8 +97,7 @@ Generate a simple URL without any transformations: import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ - privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], - password: process.env['ORG_MY_PASSWORD_TOKEN'], + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'] }); // Basic URL without transformations diff --git a/tests/custom-tests/helper-authentication.test.ts b/tests/custom-tests/helper-authentication.test.ts index 41616193..4669615e 100644 --- a/tests/custom-tests/helper-authentication.test.ts +++ b/tests/custom-tests/helper-authentication.test.ts @@ -2,7 +2,6 @@ import ImageKit from '@imagekit/nodejs'; const client = new ImageKit({ privateKey: 'private_key_test', - password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/custom-tests/url-generation/basic.test.ts b/tests/custom-tests/url-generation/basic.test.ts index ff996a9c..905c8534 100644 --- a/tests/custom-tests/url-generation/basic.test.ts +++ b/tests/custom-tests/url-generation/basic.test.ts @@ -3,7 +3,6 @@ import type { SrcOptions } from '../../../src/resources/shared'; const client = new ImageKit({ privateKey: 'My Private API Key', - password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/custom-tests/url-generation/buildTransformationString.test.ts b/tests/custom-tests/url-generation/buildTransformationString.test.ts index 6efb6cdd..c738a972 100644 --- a/tests/custom-tests/url-generation/buildTransformationString.test.ts +++ b/tests/custom-tests/url-generation/buildTransformationString.test.ts @@ -3,7 +3,6 @@ import type { Transformation } from '../../../src/resources/shared'; const client = new ImageKit({ privateKey: 'My Private API Key', - password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/custom-tests/url-generation/overlay.test.ts b/tests/custom-tests/url-generation/overlay.test.ts index 2fd3ced9..b287c272 100644 --- a/tests/custom-tests/url-generation/overlay.test.ts +++ b/tests/custom-tests/url-generation/overlay.test.ts @@ -3,7 +3,6 @@ import { safeBtoa } from '../../../src/lib/transformation-utils'; const client = new ImageKit({ privateKey: 'My Private API Key', - password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/custom-tests/url-generation/signing.test.ts b/tests/custom-tests/url-generation/signing.test.ts index dee86d63..1250e4ce 100644 --- a/tests/custom-tests/url-generation/signing.test.ts +++ b/tests/custom-tests/url-generation/signing.test.ts @@ -9,7 +9,6 @@ import ImageKit from '@imagekit/nodejs'; */ const client = new ImageKit({ privateKey: 'dummy-key', - password: 'My Password', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); From e1e5abf48ffd9845a359082aa0b8ef10adeb7b7f Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sat, 20 Sep 2025 15:06:09 +0530 Subject: [PATCH 67/73] feat: update README to enhance SDK description and usage examples --- README.md | 211 ++++-------------------------------------------------- 1 file changed, 12 insertions(+), 199 deletions(-) diff --git a/README.md b/README.md index 9e4b06a0..802c46b6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@ -# Image Kit TypeScript API Library +# ImageKit.io Node.js SDK [![NPM version]()](https://npmjs.org/package/@imagekit/nodejs) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@imagekit/nodejs) -This library provides convenient access to the Image Kit REST API from server-side TypeScript or JavaScript. +This SDK provides everything you need to integrate ImageKit into your server-side applications. Beyond convenient access to the ImageKit REST API, the library includes: + +- **URL Builder & Transformations** - Helper functions to generate optimized image and video URLs with real-time transformations. +- **URL Signing** - Built-in support for generating signed URLs for secure content delivery. +- **Authentication Helpers** - Generate authentication parameters for secure client-side file uploads. +- **Webhook Verification** - Utilities to verify webhook signatures for secure event handling. The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs/api-reference). The full API of this library can be found in [api.md](api.md). +Refer to the ImageKit official [quick start guide](https://imagekit.io/docs/integration/nodejs) for more details on using the SDK. + +If you are looking to integrate file uploads on the client-side, use one of the [client-side SDKs](https://imagekit.io/docs/quick-start-guides#front-end) for easy integration. + ## Installation ```sh @@ -32,7 +41,7 @@ const response = await client.files.upload({ fileName: 'file-name.jpg', }); -console.log(response.videoCodec); +console.log(response); ``` ### Request & Response types @@ -85,202 +94,6 @@ await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); ``` -## URL generation - -The ImageKit SDK provides a powerful `helper.buildSrc()` method for generating optimized image and video URLs with transformations. Here are examples ranging from simple URLs to complex transformations with overlays and signed URLs. - -### Basic URL generation - -Generate a simple URL without any transformations: - -```ts -import ImageKit from '@imagekit/nodejs'; - -const client = new ImageKit({ - privateKey: process.env['IMAGEKIT_PRIVATE_KEY'] -}); - -// Basic URL without transformations -const url = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/image.jpg', -}); -// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg -``` - -### URL generation with transformations - -Apply common transformations like resizing, cropping, and format conversion: - -```ts -// URL with basic transformations -const transformedUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/image.jpg', - transformation: [ - { - width: 400, - height: 300, - crop: 'maintain_ratio', - quality: 80, - format: 'webp', - }, - ], -}); -// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg?tr=w-400,h-300,c-maintain_ratio,q-80,f-webp -``` - -### URL generation with image overlay - -Add image overlays to your base image: - -```ts -// URL with image overlay -const imageOverlayUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/base-image.jpg', - transformation: [ - { - width: 500, - height: 400, - overlay: { - type: 'image', - input: '/path/to/overlay-logo.png', - position: { - x: 10, - y: 10, - }, - transformation: [ - { - width: 100, - height: 50, - }, - ], - }, - }, - ], -}); -// Result: URL with image overlay positioned at x:10, y:10 -``` - -### URL generation with text overlay - -Add customized text overlays: - -```ts -// URL with text overlay -const textOverlayUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/base-image.jpg', - transformation: [ - { - width: 600, - height: 400, - overlay: { - type: 'text', - text: 'Sample Text Overlay', - position: { - x: 50, - y: 50, - focus: 'center', - }, - transformation: [ - { - fontSize: 40, - fontFamily: 'Arial', - fontColor: 'FFFFFF', - typography: 'b', // bold - }, - ], - }, - }, - ], -}); -// Result: URL with bold white Arial text overlay at center position -``` - -### URL generation with multiple overlays - -Combine multiple overlays for complex compositions: - -```ts -// URL with multiple overlays (text + image) -const multipleOverlaysUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/path/to/base-image.jpg', - transformation: [ - { - width: 800, - height: 600, - overlay: { - type: 'text', - text: 'Header Text', - position: { x: 20, y: 20 }, - transformation: [{ fontSize: 30, fontColor: '000000' }], - }, - }, - { - overlay: { - type: 'image', - input: '/watermark.png', - position: { focus: 'bottom_right' }, - transformation: [{ width: 100, opacity: 70 }], - }, - }, - ], -}); -// Result: URL with text overlay at top-left and semi-transparent watermark at bottom-right -``` - -### Signed URLs for secure delivery - -Generate signed URLs that expire after a specified time for secure content delivery: - -```ts -// Generate a signed URL that expires in 1 hour (3600 seconds) -const signedUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/private/secure-image.jpg', - transformation: [ - { - width: 400, - height: 300, - quality: 90, - }, - ], - signed: true, - expiresIn: 3600, // URL expires in 1 hour -}); -// Result: URL with signature parameters (?ik-t=timestamp&ik-s=signature) - -// Generate a signed URL that doesn't expire -const permanentSignedUrl = client.helper.buildSrc({ - urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', - src: '/private/secure-image.jpg', - signed: true, - // No expiresIn means the URL won't expire -}); -// Result: URL with signature parameter (?ik-s=signature) -``` - -### Authentication parameters for client-side uploads - -Generate authentication parameters for secure client-side file uploads: - -```ts -// Generate authentication parameters for client-side uploads -const authParams = client.helper.getAuthenticationParameters(); -console.log(authParams); -// Result: { token: 'uuid-token', expire: timestamp, signature: 'hmac-signature' } - -// Generate with custom token and expiry -const customAuthParams = client.helper.getAuthenticationParameters('my-custom-token', 1800); -console.log(customAuthParams); -// Result: { token: 'my-custom-token', expire: 1800, signature: 'hmac-signature' } -``` - -These authentication parameters can be used in client-side upload forms to securely upload files without exposing your private API key. - ## Handling errors When the library is unable to connect to the API, From f5d2713a54e1f0a1fc3c1c36546a7ad5d3f6783f Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sat, 20 Sep 2025 15:10:04 +0530 Subject: [PATCH 68/73] fix: correct SDK description in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ddb4b7de..7403b50f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@imagekit/nodejs", "version": "0.0.1-alpha.0", - "description": "The official TypeScript library for the Image Kit API", + "description": "Offical NodeJS SDK for ImageKit.io integration", "author": "Image Kit ", "types": "dist/index.d.ts", "main": "dist/index.js", From 738f6d9ef6649d4c9288c2d05f083b2ca9211ee7 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 21 Sep 2025 07:29:54 +0530 Subject: [PATCH 69/73] feat: allow file parameter in FileUploadParams to accept string type for HTTP URL base base64 case. --- src/resources/beta/v2/files.ts | 2 +- src/resources/files/files.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/beta/v2/files.ts b/src/resources/beta/v2/files.ts index 60cc172e..95ef6760 100644 --- a/src/resources/beta/v2/files.ts +++ b/src/resources/beta/v2/files.ts @@ -281,7 +281,7 @@ export interface FileUploadParams { * When supplying a URL, the server must receive the response headers within 8 * seconds; otherwise the request fails with 400 Bad Request. */ - file: Uploadable; + file: Uploadable | string; /** * The name with which the file has to be uploaded. diff --git a/src/resources/files/files.ts b/src/resources/files/files.ts index 9093caba..0f4fd927 100644 --- a/src/resources/files/files.ts +++ b/src/resources/files/files.ts @@ -1094,7 +1094,7 @@ export interface FileUploadParams { * When supplying a URL, the server must receive the response headers within 8 * seconds; otherwise the request fails with 400 Bad Request. */ - file: Uploadable; + file: Uploadable | string; /** * The name with which the file has to be uploaded. The file name can contain: From 4bad5917155a54e60ed5cbdfd10f1c1e98e14842 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 21 Sep 2025 08:00:15 +0530 Subject: [PATCH 70/73] feat: add examples for URL generation and transformations in README --- README.md | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/README.md b/README.md index 802c46b6..0182026b 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,202 @@ await client.files.upload({ file: await toFile(Buffer.from('my bytes'), 'file'), await client.files.upload({ file: await toFile(new Uint8Array([0, 1, 2]), 'file'), fileName: 'fileName' }); ``` +## URL generation + +The ImageKit SDK provides a powerful `helper.buildSrc()` method for generating optimized image and video URLs with transformations. Here are examples ranging from simple URLs to complex transformations with overlays and signed URLs. + +### Basic URL generation + +Generate a simple URL without any transformations: + +```ts +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'] +}); + +// Basic URL without transformations +const url = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/image.jpg', +}); +// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg +``` + +### URL generation with transformations + +Apply common transformations like resizing, cropping, and format conversion: + +```ts +// URL with basic transformations +const transformedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/image.jpg', + transformation: [ + { + width: 400, + height: 300, + crop: 'maintain_ratio', + quality: 80, + format: 'webp', + }, + ], +}); +// Result: https://ik.imagekit.io/your_imagekit_id/path/to/image.jpg?tr=w-400,h-300,c-maintain_ratio,q-80,f-webp +``` + +### URL generation with image overlay + +Add image overlays to your base image: + +```ts +// URL with image overlay +const imageOverlayUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 500, + height: 400, + overlay: { + type: 'image', + input: '/path/to/overlay-logo.png', + position: { + x: 10, + y: 10, + }, + transformation: [ + { + width: 100, + height: 50, + }, + ], + }, + }, + ], +}); +// Result: URL with image overlay positioned at x:10, y:10 +``` + +### URL generation with text overlay + +Add customized text overlays: + +```ts +// URL with text overlay +const textOverlayUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 600, + height: 400, + overlay: { + type: 'text', + text: 'Sample Text Overlay', + position: { + x: 50, + y: 50, + focus: 'center', + }, + transformation: [ + { + fontSize: 40, + fontFamily: 'Arial', + fontColor: 'FFFFFF', + typography: 'b', // bold + }, + ], + }, + }, + ], +}); +// Result: URL with bold white Arial text overlay at center position +``` + +### URL generation with multiple overlays + +Combine multiple overlays for complex compositions: + +```ts +// URL with multiple overlays (text + image) +const multipleOverlaysUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/path/to/base-image.jpg', + transformation: [ + { + width: 800, + height: 600, + overlay: { + type: 'text', + text: 'Header Text', + position: { x: 20, y: 20 }, + transformation: [{ fontSize: 30, fontColor: '000000' }], + }, + }, + { + overlay: { + type: 'image', + input: '/watermark.png', + position: { focus: 'bottom_right' }, + transformation: [{ width: 100, opacity: 70 }], + }, + }, + ], +}); +// Result: URL with text overlay at top-left and semi-transparent watermark at bottom-right +``` + +### Signed URLs for secure delivery + +Generate signed URLs that expire after a specified time for secure content delivery: + +```ts +// Generate a signed URL that expires in 1 hour (3600 seconds) +const signedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/private/secure-image.jpg', + transformation: [ + { + width: 400, + height: 300, + quality: 90, + }, + ], + signed: true, + expiresIn: 3600, // URL expires in 1 hour +}); +// Result: URL with signature parameters (?ik-t=timestamp&ik-s=signature) + +// Generate a signed URL that doesn't expire +const permanentSignedUrl = client.helper.buildSrc({ + urlEndpoint: 'https://ik.imagekit.io/your_imagekit_id', + src: '/private/secure-image.jpg', + signed: true, + // No expiresIn means the URL won't expire +}); +// Result: URL with signature parameter (?ik-s=signature) +``` + +### Authentication parameters for client-side uploads + +Generate authentication parameters for secure client-side file uploads: + +```ts +// Generate authentication parameters for client-side uploads +const authParams = client.helper.getAuthenticationParameters(); +console.log(authParams); +// Result: { token: 'uuid-token', expire: timestamp, signature: 'hmac-signature' } + +// Generate with custom token and expiry +const customAuthParams = client.helper.getAuthenticationParameters('my-custom-token', 1800); +console.log(customAuthParams); +// Result: { token: 'my-custom-token', expire: 1800, signature: 'hmac-signature' } +``` + +These authentication parameters can be used in client-side upload forms to securely upload files without exposing your private API key. + ## Handling errors When the library is unable to connect to the API, From b28d7b376c90cf21704870cdd7c9401c86bac21d Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 21 Sep 2025 08:04:56 +0530 Subject: [PATCH 71/73] feat: add webhook verification section to README with example code --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0182026b..175412ea 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ const permanentSignedUrl = client.helper.buildSrc({ // Result: URL with signature parameter (?ik-s=signature) ``` -### Authentication parameters for client-side uploads +## Authentication parameters for client-side uploads Generate authentication parameters for secure client-side file uploads: @@ -290,6 +290,41 @@ console.log(customAuthParams); These authentication parameters can be used in client-side upload forms to securely upload files without exposing your private API key. +## Webhook verification + +The ImageKit SDK provides utilities to verify webhook signatures for secure event handling. This ensures that webhook requests are actually coming from ImageKit and haven't been tampered with. + +### Verifying webhook signatures + +```ts +import ImageKit from '@imagekit/nodejs'; + +const client = new ImageKit({ + privateKey: process.env['IMAGEKIT_PRIVATE_KEY'], + webhookSecret: process.env['IMAGEKIT_WEBHOOK_SECRET'], // Required for webhook verification +}); + +try { + // Verify and unwrap webhook payload + const event = client.webhooks.unwrap( + webhookBody, // Raw webhook payload (string) + { + headers: webhookHeaders, // Request headers containing signature + } + ); + + console.log('Webhook signature is valid'); + console.log('Event type:', event.type); + console.log('Event data:', event.data); + // Process the webhook event +} catch (error) { + console.log('Invalid webhook signature or malformed payload'); + // Reject the request +} +``` + +For detailed information about webhook setup, signature verification, and handling different webhook events, refer to the [ImageKit webhook documentation](https://imagekit.io/docs/webhooks#verify-webhook-signature). + ## Handling errors When the library is unable to connect to the API, From 569545c17e7ccf80cffcbe1ef847a70e4f3d07d9 Mon Sep 17 00:00:00 2001 From: Manu Chaudhary Date: Sun, 21 Sep 2025 08:09:30 +0530 Subject: [PATCH 72/73] refactor: enhance README for clarity and detail on SDK features --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 175412ea..b40708e7 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,13 @@ [![NPM version]()](https://npmjs.org/package/@imagekit/nodejs) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@imagekit/nodejs) -This SDK provides everything you need to integrate ImageKit into your server-side applications. Beyond convenient access to the ImageKit REST API, the library includes: +The ImageKit Node.js SDK is a comprehensive library designed to simplify the integration of ImageKit into your server-side applications. It provides powerful tools for working with the ImageKit REST API, including building and transforming URLs, generating signed URLs for secure content delivery, verifying webhooks, and handling file uploads. With robust TypeScript support, this SDK ensures excellent type safety and a seamless developer experience. -- **URL Builder & Transformations** - Helper functions to generate optimized image and video URLs with real-time transformations. -- **URL Signing** - Built-in support for generating signed URLs for secure content delivery. -- **Authentication Helpers** - Generate authentication parameters for secure client-side file uploads. -- **Webhook Verification** - Utilities to verify webhook signatures for secure event handling. +The full API of this library is documented in [api.md](api.md). All request parameters and response types are fully typed and importable, offering unparalleled TypeScript support. This ensures that you can rely on accurate type definitions and enjoy a smooth development workflow with modern editors. -The REST API documentation can be found on [imagekit.io](https://imagekit.io/docs/api-reference). The full API of this library can be found in [api.md](api.md). +For additional details, refer to the [ImageKit REST API documentation](https://imagekit.io/docs/api-reference). -Refer to the ImageKit official [quick start guide](https://imagekit.io/docs/integration/nodejs) for more details on using the SDK. - -If you are looking to integrate file uploads on the client-side, use one of the [client-side SDKs](https://imagekit.io/docs/quick-start-guides#front-end) for easy integration. +If you are looking to integrate file uploads in browsers, use one of our [frontend SDKs](https://imagekit.io/docs/quick-start-guides#front-end). ## Installation From cd389c7b88b8c8b6d44439a011b54e1a43194952 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 02:41:07 +0000 Subject: [PATCH 73/73] release: 7.0.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 94 +++++++++++++++++++++++++++++++ package.json | 2 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/server.ts | 2 +- src/version.ts | 2 +- 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 67dcd73f..aeda91d8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "7.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5d4f73e1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,94 @@ +# Changelog + +## 7.0.0 (2025-09-21) + +Full Changelog: [v0.0.1-alpha.0...v7.0.0](https://github.com/imagekit-developer/imagekit-nodejs/compare/v0.0.1-alpha.0...v7.0.0) + +### Features + +* add examples for URL generation and transformations in README ([4bad591](https://github.com/imagekit-developer/imagekit-nodejs/commit/4bad5917155a54e60ed5cbdfd10f1c1e98e14842)) +* add url signing and test cases ([b1594d8](https://github.com/imagekit-developer/imagekit-nodejs/commit/b1594d8e0e416811bb7a87e3d14492725dc1b2d4)) +* add webhook verification section to README with example code ([b28d7b3](https://github.com/imagekit-developer/imagekit-nodejs/commit/b28d7b376c90cf21704870cdd7c9401c86bac21d)) +* allow file parameter in FileUploadParams to accept string type for HTTP URL base base64 case. ([738f6d9](https://github.com/imagekit-developer/imagekit-nodejs/commit/738f6d9ef6649d4c9288c2d05f083b2ca9211ee7)) +* **api:** add ai-auto-description field with status options to components schema ([96c640d](https://github.com/imagekit-developer/imagekit-nodejs/commit/96c640d86b1810a122c8ba6418dd157cd0e1ff2d)) +* **api:** add BaseWebhookEvent ([dac30e1](https://github.com/imagekit-developer/imagekit-nodejs/commit/dac30e1479b5f022c0d59c0bd84ee928ba676dd2)) +* **api:** add new webhook events for upload transformations to enhance event tracking ([dd98040](https://github.com/imagekit-developer/imagekit-nodejs/commit/dd9804078ee46a656f8423de2845482bdaca6be8)) +* **api:** add signed URL options with expiration settings to enhance security features ([55d2dd1](https://github.com/imagekit-developer/imagekit-nodejs/commit/55d2dd18b0c717a5ede4fea09523098d806e87af)) +* **api:** extract UpdateFileDetailsRequest to model ([30d976b](https://github.com/imagekit-developer/imagekit-nodejs/commit/30d976b95ae76c09bc9152badd6bed801bf3cf57)) +* **api:** manual updates ([608ef99](https://github.com/imagekit-developer/imagekit-nodejs/commit/608ef9945b576180c3786380262b1a4074bef456)) +* **api:** manual updates ([d0d45ee](https://github.com/imagekit-developer/imagekit-nodejs/commit/d0d45ee5351438651649153cb70ed8d9809078a1)) +* **api:** manual updates ([78f9507](https://github.com/imagekit-developer/imagekit-nodejs/commit/78f9507314187a0a925eb095168caa5759b7d42b)) +* **api:** manual updates ([af5fd2f](https://github.com/imagekit-developer/imagekit-nodejs/commit/af5fd2f465f9d00d4822e1fac77cbe323094bd33)) +* **api:** manual updates ([2ac7656](https://github.com/imagekit-developer/imagekit-nodejs/commit/2ac76564b2119db0d9d4eddb399ddf422ecc6eba)) +* **api:** manual updates ([d208673](https://github.com/imagekit-developer/imagekit-nodejs/commit/d208673da821f783d8279d6eb22bfe1e41ee4f62)) +* **api:** manual updates ([76f3ed7](https://github.com/imagekit-developer/imagekit-nodejs/commit/76f3ed799e69105013d849c0d94de6778ab4da7a)) +* **api:** manual updates ([01bdaa0](https://github.com/imagekit-developer/imagekit-nodejs/commit/01bdaa02fe0d6a5d5fcdca09edd31d4562031ca7)) +* **api:** manual updates ([9d913fa](https://github.com/imagekit-developer/imagekit-nodejs/commit/9d913fa2de488eed9e5be5d4ec10b5ad83335c62)) +* **api:** manual updates ([dc932e3](https://github.com/imagekit-developer/imagekit-nodejs/commit/dc932e36e7d79742e2d1d39a8a4aaa7b667b85c1)) +* **api:** manual updates ([50c8520](https://github.com/imagekit-developer/imagekit-nodejs/commit/50c8520ab96f5e96dcb50ca3964be1f21acd1dec)) +* **api:** manual updates ([1d0423a](https://github.com/imagekit-developer/imagekit-nodejs/commit/1d0423a6b3866f9ad2cf65a09d0e9f902930c37e)) +* **api:** manual updates ([64fc454](https://github.com/imagekit-developer/imagekit-nodejs/commit/64fc45473e4072df18cff73024bcd4469258bf65)) +* **api:** manual updates ([f70d1c2](https://github.com/imagekit-developer/imagekit-nodejs/commit/f70d1c2fc248efb16b990e047796bf7aab5387c4)) +* **api:** manual updates ([4efbfee](https://github.com/imagekit-developer/imagekit-nodejs/commit/4efbfee0ca0de866a0ad77c607d7d6fb14a05c84)) +* **api:** manual updates ([174eee8](https://github.com/imagekit-developer/imagekit-nodejs/commit/174eee861dac548093cc6b561eb59496cb5539cb)) +* **api:** manual updates ([1b740df](https://github.com/imagekit-developer/imagekit-nodejs/commit/1b740dfb1e21293568614f5a7fe96468762f5286)) +* **api:** manual updates ([636a5a9](https://github.com/imagekit-developer/imagekit-nodejs/commit/636a5a991e4e648da2d183a6492e9a959938b2ec)) +* **api:** manual updates ([c1bc59b](https://github.com/imagekit-developer/imagekit-nodejs/commit/c1bc59ba35af6b0e7bac82e1e87e3937eda72cf1)) +* **api:** manual updates ([4d7286a](https://github.com/imagekit-developer/imagekit-nodejs/commit/4d7286a5b61168b8bccd44e2cf754938e63c8568)) +* **api:** manual updates ([8986981](https://github.com/imagekit-developer/imagekit-nodejs/commit/898698108afffb5ecffda06765b7c02c21f2e74c)) +* **api:** manual updates ([693e3cf](https://github.com/imagekit-developer/imagekit-nodejs/commit/693e3cf68ccd5a8de740ed35b9d0cc2660e88521)) +* **api:** manual updates ([ace1909](https://github.com/imagekit-developer/imagekit-nodejs/commit/ace190977c46f6702597fb4d6ea54133346724a2)) +* **api:** remove Stainless attribution from readme ([454c722](https://github.com/imagekit-developer/imagekit-nodejs/commit/454c7225ad3fbfda4f6807a0655b5d0b430b16d8)) +* **api:** update api docs link ([34d2eb1](https://github.com/imagekit-developer/imagekit-nodejs/commit/34d2eb1c9de598e7f01156588a8f942dc36f8a70)) +* **api:** Update env var name ([70c98e0](https://github.com/imagekit-developer/imagekit-nodejs/commit/70c98e08925b1884713e524129227003af75c7b6)) +* **docs:** add URL generation examples and authentication parameters to README ([7a2bc8f](https://github.com/imagekit-developer/imagekit-nodejs/commit/7a2bc8f71d50a730fa7ebf634d2775c30d21171f)) +* **docs:** improve descriptions for private API key and password fields in client settings ([7ab6b37](https://github.com/imagekit-developer/imagekit-nodejs/commit/7ab6b37f00f0b4ecba52bd4814370d22c5264c7e)) +* **helper:** implement getAuthenticationParameters method and test cases ([297bb95](https://github.com/imagekit-developer/imagekit-nodejs/commit/297bb95dabb0ed878bd009e1878b418ed26bf31e)) +* implement serializeUploadOptions function for upload option serialization and add tests ([cfce32f](https://github.com/imagekit-developer/imagekit-nodejs/commit/cfce32f9b706a52035714714dcbc8429e4072f04)) +* remove password field from ImageKit client initialization in tests and documentation ([08a5744](https://github.com/imagekit-developer/imagekit-nodejs/commit/08a5744777862dcb8b156ed47b31865db2c9f837)) +* **tests:** add test for transformationPosition as path in signed URL generation ([2f37641](https://github.com/imagekit-developer/imagekit-nodejs/commit/2f37641776756aaae377c802f46e2ee6349127eb)) +* **tests:** add tests for transformation handling with absolute URLs and non-default endpoints ([188eeee](https://github.com/imagekit-developer/imagekit-nodejs/commit/188eeee3b77d7e3a89e8c5abad4e0fef0ca9107f)) +* update README to enhance SDK description and usage examples ([e1e5abf](https://github.com/imagekit-developer/imagekit-nodejs/commit/e1e5abf48ffd9845a359082aa0b8ef10adeb7b7f)) +* **webhooks:** use toBase64 for webhook key in verification ([433eb44](https://github.com/imagekit-developer/imagekit-nodejs/commit/433eb44c54f3211d1b80aa97935a705ce7968a8a)) +* **webhooks:** use toBase64 for webhook key in verification ([3d0571d](https://github.com/imagekit-developer/imagekit-nodejs/commit/3d0571dbe9fa9cdd04f23a2f6d56a49005596649)) + + +### Bug Fixes + +* 24 ([5610765](https://github.com/imagekit-developer/imagekit-nodejs/commit/56107650b674572551057c3788e0857ece5e5e7c)) +* add repository details for package ([b9e4231](https://github.com/imagekit-developer/imagekit-nodejs/commit/b9e423142ab909ce9f0034e73d26c6d350ade4da)) +* added folder object in ListFileResponse ([#106](https://github.com/imagekit-developer/imagekit-nodejs/issues/106)) ([bfcfbb9](https://github.com/imagekit-developer/imagekit-nodejs/commit/bfcfbb9ed2c82aea7284ed6841d3a92afd2fb0da)) +* coerce nullable values to undefined ([66e3b81](https://github.com/imagekit-developer/imagekit-nodejs/commit/66e3b81cc8d6a1a123c0622c08801ecbdeef4f9f)) +* correct SDK description in package.json ([f5d2713](https://github.com/imagekit-developer/imagekit-nodejs/commit/f5d2713a54e1f0a1fc3c1c36546a7ad5d3f6783f)) +* **docs:** add missing commas in URL generation examples for clarity ([21caa93](https://github.com/imagekit-developer/imagekit-nodejs/commit/21caa9336a890568790d5b2bb49c274ed2434c4e)) +* **package:** removed unnecessary types and install-types package ([a254d4b](https://github.com/imagekit-developer/imagekit-nodejs/commit/a254d4b5f5cef576fba3499d77cafc13b521f7bb)) +* update privateAPIKey to privateKey in code and tests ([2f93b89](https://github.com/imagekit-developer/imagekit-nodejs/commit/2f93b891233782e8f6af350905a979f683173458)) +* updated signed url generations for urls with symbols and unicode characters ([#102](https://github.com/imagekit-developer/imagekit-nodejs/issues/102)) ([5e264de](https://github.com/imagekit-developer/imagekit-nodejs/commit/5e264dedf6b5fbc9e98b66e715726eb7b2b1cfba)) +* **webhooks:** revert toBase64 conversion for webhook key ([13c716e](https://github.com/imagekit-developer/imagekit-nodejs/commit/13c716e35e73c8ad79157b818ac93b45365be8f3)) + + +### Chores + +* bumped package version to 6.0.0 ([85c7ef3](https://github.com/imagekit-developer/imagekit-nodejs/commit/85c7ef34f4c624d3b292ffe4115718607ec1e98d)) +* ci build action ([06a9882](https://github.com/imagekit-developer/imagekit-nodejs/commit/06a988278c597a54f8d7e7b5c23d62cfae4079b7)) +* do not install brew dependencies in ./scripts/bootstrap by default ([69968b1](https://github.com/imagekit-developer/imagekit-nodejs/commit/69968b160ccf3e4dbd68a6356714d75dd0d63acb)) +* **esm:** Improved Support for ES Modules ([5a4127f](https://github.com/imagekit-developer/imagekit-nodejs/commit/5a4127fb4c3b6c7d007043cf51d3c0687ef68ac0)) +* lint and format fix ([788885c](https://github.com/imagekit-developer/imagekit-nodejs/commit/788885c3cc5e8834105ec2b0b8ed28ac747b0b1a)) +* sync repo ([3b95a96](https://github.com/imagekit-developer/imagekit-nodejs/commit/3b95a962395d62aee0c8133efce3bc863a0332bf)) +* update SDK settings ([9ea85e3](https://github.com/imagekit-developer/imagekit-nodejs/commit/9ea85e33b6484aa6a62c178c51d9522756750297)) +* **workflow:** added node 16 and 18 to test suite ([ef277ca](https://github.com/imagekit-developer/imagekit-nodejs/commit/ef277ca3e3f7d3801d9ea7a54929a9cd47837134)) + + +### Documentation + +* update to make it more readable ([ed5ff38](https://github.com/imagekit-developer/imagekit-nodejs/commit/ed5ff38d6d9576a70c8115d9ed1e54f537277d8a)) + + +### Refactors + +* enhance README for clarity and detail on SDK features ([569545c](https://github.com/imagekit-developer/imagekit-nodejs/commit/569545c17e7ccf80cffcbe1ef847a70e4f3d07d9)) +* **helper:** remove console error logging in Helper class ([cc1a4c0](https://github.com/imagekit-developer/imagekit-nodejs/commit/cc1a4c0d915a9dfc6b1156f578fb1e713f965c2e)) +* **tests:** remove redundant helper tests ([ef30e9c](https://github.com/imagekit-developer/imagekit-nodejs/commit/ef30e9c65b9259bbc5bef259a565789c1502dae8)) +* **tests:** remove unused imports from URL generation test files ([2e7211e](https://github.com/imagekit-developer/imagekit-nodejs/commit/2e7211e34f56a45e909db054a5dc739dc824d6e4)) +* **tests:** update URL generation test to include new aiEdit transformation parameter ([a18331d](https://github.com/imagekit-developer/imagekit-nodejs/commit/a18331d25a731109106a8e7c5c63a884e851d854)) +* **transformation-utils:** replace safeBtoa implementation with toBase64 utility; update overlay tests for consistency ([e4adc14](https://github.com/imagekit-developer/imagekit-nodejs/commit/e4adc14a0662f9782665bdff8865229819618995)) diff --git a/package.json b/package.json index 7403b50f..d250a709 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imagekit/nodejs", - "version": "0.0.1-alpha.0", + "version": "7.0.0", "description": "Offical NodeJS SDK for ImageKit.io integration", "author": "Image Kit ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 788cd893..d9d27302 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "imagekit-api-mcp", - "version": "0.0.1-alpha.0", + "version": "7.0.0", "description": "The official MCP Server for the Image Kit API", "author": "Image Kit ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index d9bcbcb2..789f894f 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -33,7 +33,7 @@ export const newMcpServer = () => new McpServer( { name: 'imagekit_nodejs_api', - version: '0.0.1-alpha.0', + version: '7.0.0', }, { capabilities: { tools: {}, logging: {} } }, ); diff --git a/src/version.ts b/src/version.ts index db692bc9..e1f023d3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.0.1-alpha.0'; // x-release-please-version +export const VERSION = '7.0.0'; // x-release-please-version