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/54] 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 ed723b3..0000000 --- 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 0000000..43fd5a7 --- /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 0000000..870fe3a --- /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 7263c90..0000000 --- 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 e1b73f8..0000000 --- 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 919ac52..d98d51a 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 fe5128b..0000000 --- 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 eaa22bd..0000000 --- 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 be4975e..0000000 --- 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 0000000..7cc13dd --- /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 0000000..af75ada --- /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 0000000..67507d3 --- /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 0000000..e4feee6 --- /dev/null +++ b/Brewfile @@ -0,0 +1 @@ +brew "node" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 7ec5268..0000000 --- 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 0000000..ceb9760 --- /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 0000000..e7a4d16 --- /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 37a4832..0000000 --- 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 dbbf7a5..3282e61 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 0000000..8e64327 --- /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 0000000..15beb06 --- /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 a24dfd2..0000000 --- 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 0000000..45e8aa8 --- /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 0000000..c1a01a6 --- /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 0000000..0651c89 --- /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 59d984f..0000000 --- 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 0000000..f7631d7 --- /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 ba10e6c..0000000 --- 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 fb0e649..0000000 --- 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 9329db3..0000000 --- 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 eef7cab..0000000 --- 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 c29d1a0..0000000 --- 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 6f7c002..0000000 --- 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 7efcee0..0000000 --- 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 358ac53..0000000 --- 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 aaa9bc9..0000000 --- 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 642310f..0000000 --- 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 f6b6cb7..0000000 --- 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 b2c489e..0000000 --- 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 b33ae42..0000000 --- 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 a53ca4f..0000000 --- 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 122a4de..0000000 --- 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 bba9321..0000000 --- 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 1a26a16..0000000 --- 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 2361a6e..0000000 --- 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 c7b0e38..0000000 --- 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 98b338d..0000000 --- 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 a2990f6..0000000 --- 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 47389b2..0000000 --- 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 9ca4437..0000000 --- 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 d3dc6ac..0000000 --- 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 538a897..0000000 --- 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 194afdc..0000000 --- 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 a3bb4ef..0000000 --- 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 752ab08..0000000 --- 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 5ee18da..0000000 --- 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 72f4ac3..0000000 --- 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 8d17ace..0000000 --- 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 7d1a857..0000000 --- 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 8731997..0000000 --- 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 35cd2d5..0000000 --- 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 40f220e..7eaf631 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 0000000..a672615 --- /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 0000000..2eede58 --- /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 0000000..6c2868f --- /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 0000000..09d365b --- /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 0000000..2c75a6c --- /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 0000000..15ce7f5 --- /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 0000000..02e7e89 --- /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 0000000..865c392 --- /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 0000000..e0e2d2e --- /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 0000000..f84053c --- /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 0000000..47d60e0 --- /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 0000000..1aa9a40 --- /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 0000000..d5162bf --- /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 0000000..c11185b --- /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 0000000..c450e4b --- /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 0000000..2100cf5 --- /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 0000000..d30dbdd --- /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 0000000..d902a5b --- /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 0000000..7e516de --- /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 0000000..c189d4a --- /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 0000000..d7c62e4 --- /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 0000000..a155869 --- /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 0000000..1effce0 --- /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 0000000..a665117 --- /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 0000000..cc9742b --- /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 0000000..c323a3c --- /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 0000000..98d9bba --- /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 0000000..3d27514 --- /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 0000000..ec8140d --- /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 0000000..2035c6a --- /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 0000000..2cc85df --- /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 0000000..acac59c --- /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 0000000..1bccd5a --- /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 0000000..4ef295b --- /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 0000000..a55ee9a --- /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 0000000..bb20821 --- /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 0000000..b9d2328 --- /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 0000000..ae7291f --- /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 0000000..f6a051c --- /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 0000000..b41409b --- /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 0000000..f54f024 --- /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 0000000..d51184d --- /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 0000000..d7002a5 --- /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 0000000..5cc7e0a --- /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 0000000..d7e6959 --- /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 0000000..f6ce3f0 --- /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 0000000..b3758ad --- /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 0000000..a4b07ec --- /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 0000000..16d682c --- /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 0000000..bcd87a7 --- /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 0000000..6479106 --- /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 0000000..5688f6a --- /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 0000000..ecb444c --- /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 0000000..94765bd --- /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 0000000..177dfc8 --- /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 0000000..6a74898 --- /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 0000000..0182363 --- /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 0000000..ddd2fdd --- /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 0000000..51a6af2 --- /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 0000000..0d03598 --- /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 0000000..3640cb2 --- /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 0000000..ba7083d --- /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 0000000..8106d49 --- /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 0000000..d6272f6 --- /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 0000000..08963af --- /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 0000000..a8a5b81 --- /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 0000000..cfff24a --- /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 0000000..4facad5 --- /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 0000000..047e086 --- /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 0000000..e9f2d70 --- /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 0000000..ddbe007 --- /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 0000000..707a2de --- /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 bb0fdc2..0000000 --- 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 032f2d9..0000000 --- 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 fb22c51..0000000 --- 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 0000000..062a034 --- /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 0000000..fe15966 --- /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 0000000..4e5ac01 --- /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 0000000..7a75640 --- /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 0000000..3ffb78a --- /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 0000000..0b28f6e --- /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 0000000..50e93fe --- /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 0000000..7bce051 --- /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 0000000..b3477c0 --- /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 0000000..1354eb4 --- /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 0000000..86c56df --- /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 0000000..e5e10b3 --- /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 0000000..79d1888 --- /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 0000000..4d6634e --- /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 0000000..deae575 --- /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 0000000..1d893fd --- /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 0000000..8c775ee --- /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 0000000..9c8c3e2 --- /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 0000000..485fce8 --- /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 0000000..53821f1 --- /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 0000000..47d1cc2 --- /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 0000000..5152510 --- /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 0000000..2882ca6 --- /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 0000000..fc55f46 --- /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 0000000..71734d8 --- /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 0000000..3ef5a25 --- /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 0000000..c23d3bd --- /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 0000000..e82d95c --- /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 0000000..82c7b14 --- /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 0000000..c724a9d --- /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 0000000..10d1ca9 --- /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 0000000..2aabf9a --- /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 0000000..8ddf7b0 --- /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 0000000..e4dc710 --- /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 0000000..245e849 --- /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 0000000..b668dfc --- /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 0000000..bfc86b4 --- /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 0000000..3cbfacc --- /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 0000000..2312df3 --- /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 0000000..8da627a --- /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 0000000..2d84800 --- /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 0000000..7da4129 --- /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 0000000..dbf0664 --- /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 0000000..65e5296 --- /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 0000000..b0e53aa --- /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 0000000..532f2c3 --- /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 0000000..7554f8b --- /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 0000000..363e351 --- /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 0000000..b283d57 --- /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 0000000..b9db299 --- /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 0000000..82d1b23 --- /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 0000000..34a0246 --- /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 0000000..39730cf --- /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 0000000..7bd424c --- /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 0000000..87e703a --- /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 0000000..19a35ad --- /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 0000000..1542e94 --- /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 0000000..f8fa04c --- /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 0000000..2b3a43c --- /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 0000000..ca56a44 --- /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 0000000..17bc06b --- /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 0000000..bffa32e --- /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 0000000..3a04792 --- /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 0000000..c011d11 --- /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 0000000..b959081 --- /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 0000000..3c9f4fc --- /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 0000000..d06b270 --- /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 0000000..10a917e --- /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 0000000..46a5299 --- /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 0000000..35dec26 --- /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 0000000..4e6b5a5 --- /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 0000000..e194b45 --- /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 0000000..1841640 --- /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 0000000..4cd160b --- /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 0000000..7aaeee3 --- /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 0000000..916e304 --- /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 0000000..7759a7d --- /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 0000000..a9fc92b --- /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 0000000..eb7f8ff --- /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 0000000..1c7f693 --- /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 0000000..770f5a1 --- /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 0000000..b2ef647 --- /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 0000000..55a1a52 --- /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 e9aa713..0000000 --- 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 0000000..6881e30 --- /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 0000000..4f09652 --- /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 0000000..5e83c3c --- /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 0000000..a67f9a3 --- /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 0000000..1012110 --- /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 0000000..0354e08 --- /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 0000000..9ebf4d3 --- /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 0000000..823b35b --- /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 0000000..8969d90 --- /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 0000000..8b2c74c --- /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 0000000..24800f8 --- /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 0000000..e42b641 --- /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 0000000..3e69820 --- /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 0000000..f3b1af6 --- /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 0000000..5191005 --- /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 0000000..818d0b8 --- /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 f1f7b2c..0000000 --- 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 18ad8ca..0000000 --- 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 6d7d2f5..0000000 --- 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 zcmeFZXIN8Pw>G?jQ9<2OB3LQ1A&7{82na$b1}O$22mykEhE52PDjhbW5>QY;DFLEX z6+$mc2_hgO1f&ZHD809Yl2ClJpYz(z*7H5*`o16Ek7tEk^PVGfj&WzKnOVo;%ZD$Y z050A0I_Cip2n1-eUci?jwj(;{&RJeGM4s0{XtF*40Jc3&Rya2{E&#weySo}{X$YB` zp@nvS1VF$+U>hI~C|ID$~Tm zYIZ+h%n$hg+GQ01wppF}JNf_ia<}nf@dp4MQvmqer~jq>yRW{>w(+qj$g=K5+IJZ~ z1pv-0v*h=`%P{c(P7$F{_fH)-8+CrW!Hp}k*y}#q3Hb?r*$!3y?KP9qU%q_l144l9J9g~k*tu)> z?p=TEWxXIgTwFX5eqKJ-i(lff&|%h}1oR~IPu3eQFD)$(M{1~QAT5oJExla55)<1% z|8Et1c@1#0v%lLz+y)W`*tkL4xItfPfg`L2# zZy){#{Ko_T@xXsP@E;HS|HuQ{K+yk=4LIdeth{%;Q3D)WT;O7t?Y%Jk*Z)cbICpAU zbRX^0n;?KA@nt3Ck=0&KQLecEjgE$y7VM?E;QYVqL=9Bw4fzcmSNr!BfD~4kMvGUU z|EmTBZ&E;Cxcd0tul^`d-*oY++P^A*PB4a5qpAOPWsnrg*Sn(i|CZ-4@0qH)QycW} zRu^lZjD#NhTAYW3FBc4N_;)LFG|Z6{9)2s0m{B>Pyzi^7Z{ptol3nV~s65u}O-3Z6 zy9m+sG%STRI|ZNv>x_--zp3-z#326&OHP)3wrdIv#O}#upUQ*BN_L@_;9XNzbz_T+ zJnu<%0037ezjK`WZ&iPjJ*eWXZ2A}gE=Xk#7A^bO?T`}Sls{x8j1>=!$yKB=RbcVC zr25Ybj7b)8#S)Ls;om6wSqxJ2?^t*b0FV4{4_+hgCsu!|(9`JXr`hx?WF{x4VK-4Mh$?@l* zcjGJjj?g=&{Ng?VY`9{_#h~9P`blivbG|?b;2@pv^PgFA^jH(OkB<-6xU?V4BO#tG z424Sa#)Z|6^*Ajt+{>ES0dQ^}7W0#W-(`ZMqwKsk*6W( z62j9Mgf_}HpBsg=9d93hkCjmaAkhwY%zvZmCoyE2KD`Kl6nf~CFTjrIdV=h!7^>|> zU7cra?C5=bnz&O0JsWN#^SRMkaTo zP88H;?2E}p2xC&fkHguc@@0?3#|P&h==v~M;BeZM3lN=Kc3gb*Yh^!5**rbEI{;`O zEE1R2cJ<~_kzQfZW10`xV^A0Gv$S#`L>;rN)3T{9ZT^S-NjHg$Lcl59_35iW>i9L@ znRzXz8URJ(I|maxGi)#-P+JK$tTen<*D)R*8j@~CfLKdFWRE3u9c#$%_kKzBS7Y<{ z{R8!DZ9hp%-jz)N5dRkau?qL1e(|_a^t)PPdtJ<~UT}(b5qsRjEcW=245-0{x*-hj zUC)*IMViu7(DsrK%lCiM^Q#Q&w{&Pj8qh0ML7jA~hd2=7W*i#An8IuJ*_s&P&V&q6 z85!$$?R-H9U960FdP*m;?%F+pO{#Z4P7TalK|5XlmDZnRJRi)_L2P}_1&QMk7Z42) zYq(khnU{=-3Q1|y8(TqKI)XaNQ&)7-ms=jsm%63yQ-ea=0rESm|mITrM2ZPgz@5V_Pf<^ zC=>!FN7!P7O);K_t2hGKAgXW?=>3MPr&KSGb0JCUV$CmNoDKq%Oyr}8D1*7MTa<2gnr6yW>yUSFqDtW z=+;Vm10YkenRP!J`6W)ew`>oXd-P1beBRWHy(Vi)K>H<+%?}(T)-}kP(4I%h#v&HOFdwdevZGglL7FaFgYhiG+^#zgrUm!M*^fgkPEX zQI=SagGqy#zDP^4(fF8mCvMs5o>wC0eWcH-%4S!qg+oO|Lb@9Na;XZ%&(kx6pID}_%gIBlM{McBtA$ejv$0UfOR$0;(}SD z%;576Ze^VN`eh@QGQC^ur+v{Pa0H@F!nDVIy81UdcS5>`5Vaxj$HEt^muLM0k|L|*j$h)JX?60R zf3!-bUF)9S@ba+OV;n5PQxpQ%+e~sEj(a~;854iYP{JfNqs?uZwJ~NH`m1mGO~$cm z+xco|RwZp|xd6A@P?RqXRSs+Ed2OGsVNFm&8OSPn9+vVPv_W58blk8wZk3~y<=Awh z{G=tayuV;|U;w9IX#T8Tmki-S)TO+ujvef?9+q1{K)KnAP}%(oH)9`fFI7rZ`)2PK z00hfDUjnEEYIM>bV0nrqWMt99Ep5w}TLedLrL@YhBiOc*-qd*HljROwxsec)ZY&`e z$JQ#3cI{KBa(J=gbKTy9iR4pi;)yFjg>~Vh1H0|LD{bYpn5QLTWiI*(04okMB7)(jsqn8 zdj|D%hK)~clSD-FTz5Z>s;?M-l#RVA)3>t!{GG(^p?d9bP59q^&I5=z(OfFaw{3mn za<|Io6h4G$Txd9Rz2c*DNmubDZti^*wRoD#T98U|zw%dm96?{v4=Kk4-4s9;PJA4D zfWfpLMYnr=oLCDyYL5^W<(6NetL$j3Yna?vTkMDnWf=|$V$;^eVaHe9D@ELn%w-)x zAbO#fcy9+}J{_ieybETpmuTzXVR14@tPghJ2P^-8kjjNMp`9$VEB74qY=tjzn@uqX zAaQjv@*%Apw<`MuwWiu#2RP-GYCCy?{;ti2v0Z7HnJBybabE6uJ&#^UEy@+QzUBL{ z-u=D-w_{&Q{o4-koN_YPKTLfWfQplh0Qx56LJEPXCwUP?iD-18kyUJt>A}UiJMjJq z7AEy$Y49#bfK8N#J?;Sq%jzXRO46#CznDaJJx?B5g6G4HGizS}QL?PrEiAW#dw?_Q zv6yd0?RO*pM9a7$u5AkME0lX1gi|_9`up3*liA~iLStm<{_^#j@1_@qv!4JU9#&0m zA#k)Wv$QfWHFjjfgD0L48t&t(SH+QA@eV4%%}X_#KK-!+s8Vk+`lp$1lI=_Z&&Q{3 zl#C4F#P3?yYRZ2B)Z4CG*dtyGg$%m1&o5sNC~t{?3PV9WU~TpoT__JA4A<6?Q3YC|u7C zU!E+Xg@u-6%aDCWCK-}`ptF8H|I=&}H<<-e5xiDoMMQKtK-fbEZhX`d4>LUW7csD7 z_9YBkA6Co-7GV?R2niAbVh}nUKoMWVMBk0lk(Wqic)EQYfkLAU(PgpfMh4hR?VZ;L zq#GsZQQ%MpUiK0HyK~eR)@`%KdF8_ecdWl4PfUW zfSY<7;HZ26&YH5r!309Mxm021wEIW>oG4L|^Ln|h-g|xUa>dxww^HPZIs4i7B<+{` zYUCH`Zg;;~fMnA4QqP(?RcuT&%x_%{JTq=zaLrVsZ>}8MKnplw91PY5vuV<1JO}!i z{_zFPaD(ys@P_b~oK7#7Y9I?)B9HQTz8LquYf{LL|7BI zjF7r$rT@tq@97#p0V%=AhDcs<9(DN=1Y^^p#j-#m_R7=NN$JNNLHDEFd`dT)Nq0J` z#Sxunb5jz8d~#3TdOou-&*azcZ{`Gy-}p5=Y0AXr`mG zrbbC5hq4iG670Azh)4Kh%(_p~n_)gnzC!cS>acv%Pz?i!buSd>G;r^B-L<~;ux810Rs@CN>-Y2xOrBI;PUryOw=Kfc`VE8>JpZaCP2BR zV{}c5b7KdKKiXE^yzq89h3%rYsC;AIY)PV>q)xD{@m*r`>dnrPm@IAserQ!fkXZWo zLeN*c|48?23Q7W&mGxBgQe2o0FMU%ieklJ$h=%Ycr{xS|EO$FJ58xKd%AP23A-7^H z))w3H`HY@PC#2ZgLb=KS~=pw5Uc=S1&E1vQ89~8a`kY-KS*n9*UZ)Xp57TX_hpmQoT=R-Sh*$QuJS&e`8 z@WKcK>^d_uKFS?!4jZP6sC*7_be42zNJgtTqAnLqT{3Sk>7Pl^zm)WxgIh*Cgj$(o z;#=ne^D4%jx~X7Q{wdGmYS{ATpz&LIAp5?s?%H^V2_%ny(`~HGy*vje z4A+1fNFxv#&x+%rID3oLhWrW7wW1AJ0T2?BvDF-2KIV0TTRa|)teRA=S8&LOqikbm zyf)STxRa&#&GJ7$(rP#wJhx8aDU@hJLB;i2S}9gf2t~p|Rc77TGo&57j@&|G9{o5O zw*K`~_PfD{H7d$KX@!=<>P>#qnP@rLP%fO~b<*Mu+G7oj`{ zbw0^Gs?3tR>uS>7zVQX%`uivQ4+dWa+nE&o$9BxfKXWRhW$%V{x6_+dBkM4jG|Q(m ze#&Au_rcY`Iuo-@Uwd}}Wp@M9oa34{>dQ&7oqx1+asUqt ze3<%QfUu!F&5TnT`2o?npElwp%~3J~_j7eVj|p+=k0*V#{Ew7l>a>NlSYhuH#G!~P zemskQXk&Q0^D(j1*^LUms(mOSHqo<`X`7+3VlSDDn6Pes3=do#!se(0;edz}2n3>y zg%jm+awTiQ+YO6i`iJm8dPl7;gP_0f9m(2V1?a6Q|n za9t>X07>gwYZEO+bBLpq!K{{C##S<$h$Oe)Kyz*ZV_wqGBq3{-c|*L}qS0^{irzmy ze0&FPT;(6W_$uI-EP{D$r`#GJ$9u)JL5m{;J79J#C?^!xht+baM8}1?Yk;st%M)k6 zk0phBm1?DjvY#8XbAh#mF&ZK|;yUuS4T=(quIozv^U+}=jzE;1RB7c$zv**kd7*O{*=#;4tbmB$Tsxd&9|p-+X))-%3!0+5q_ioDe^{GLlUCM*%1-L( z7g$~ZUMi@*GUc5saJ=gQ!bk)9{62zP8UREPkW-R;4N+Z0bNPVCXSrgptXMnjwyeF} zNvXss9le;u%16`V%S7HHD?QJ%0X&awClr7C8wRPIjC{9!>tSk#1&)t=$+W)1Q&n_P zN{08Q6z+o`O??FQm|J|ZJh5WsCN<)~WJFOYAv6RGr~x7zAp}Iae4{TKM}+m|&?hJk zV_@w#qD|lCSv^{=xIGcBRXF*vMHbupG{Mle(nRtZn~#3rZ(GQoBn3&pxxATI_Eeae z_R6No=*E;a`NFb)w^+TmI5)s8`@z$UAt{v{T6eoRG&H*-P7PlB3II5uP|;HlqoXCs zo=#f>rPs0u{0_E0?lWTh?n9slL0r>Wakw>{N~00 zVEUol1AI)2n`W}Ij-7I37{M|DW8wJxaPw+owU-P^2q?vU&^kz_bxB12A$-(WCj^Y( zx*zgDMn*`4hk)pj5ATz|aQ<>~PL?;Bk&(~AhUrncg1kXX#ylg!Yi*c^Z=*+fvwM^5 z6jFka7n(2rjtAew%eUaU@&OKqcvMsvJ|KLF-_gc2gh{zs!HCFY%VQIDp3craoK}MNP)} zOeFWS2CsxV2kh|jvZ~^norQfFzoW9Fo3CQGziMI^qN(?xCW=Xts5QiK?GPQz!XhjIun~kg+s~JlJw4jlP5)d_D#9MG$V;7h^xTmYIU-%A0Dxp#+|%%Yo;7x7ga{ zB`z3P5Q`Xs59~t|y81bN(}w(vvemdOi)XI8^lt3BB%tiVm8~nJwtHQLi1qf{lG=MF zh=-a-w{3|uAEluHL~U$*af%x?S8^NY9~>UV=FIN`jjW%+XNT_omc17|J?bBv4CN zx{;sLw4*M`7uA}d#A;S5uJcneN8>{87a_)bHZN$>a+NdM!kDlms$0Bd#8l@&8Jy?k zBcRp^m)|e~q{>^>#3l|f8sBxH3L{ihQH)&q0+%OelfM8p)!s6OT%b}WKJ{J)V@R4Y zt&;{n3f?B)hZ}8Mn+gJA*e>#vR^ei`KBvGla1aNnXwe?o^|Mle3N$Z}xb?C-Nu4n- zww)|T__(=qJo$34i|)0)jrlh_Q}IuNenaSw(&)E4Gp8!{o`vFgQ8Ftk`x`cBHo_2M zJStM*1x0g#Lkw=@*Wvy7K~vh+Ya9-;Th z5_-X+XGJA8H)QxKTPt>k^4BZ_9|5&}SGXyt(`2y-;Uy%v!vZuwDq;%1;RA@JjUVTj zV(wMyA`}!SXr)dE%8U!+OrJ}&uYSBCHdc>r0;<}Sa62W&>d-^gX6cR^+|uUP%Ilvj z39+FXd`F6B?s`q?8a|C-v!Isn!_LH{A^K$%$-aZI;hbD5T}^~&JK(Q%XUM+5#@spJ z0Zmn<-7HNnD>jdjBdq|l{2i@5>;C)Lz5qre74lK-y3J>%d@R@snEOGTX)&hMYMLP_eNbUz<~Quv zZXEgu-~wW4uNSR{6S~DZw{B3?E^KUT<>^SfBppu37gg@XP2 zz5oJu4uRz9zABNb(Tx!tN2T>GO)^!K2Tm9V9aK7j)@>EOBp=hK!s;GWJm-@b>DPoc z3vraph6S$-{Qhh$^^Y6l0`Qt6=no}lop*uGZvLV6YkHuwR8e)68Mp56x@V^o+Mmo* z6#nAKKG#i;v}~$4Fnpw1TGk)w@y34Pyt1kNT^A#Dwm$#nJ7!!Eg_T(uhQ}HPH{a(A zZHqD4A9-mwTR$)`m-9|z1dv1-R{#7oCc`Pd!LNi8c*A9e(ZZV7#ucsm?kyZ;KE0gK z-Mze50{N&_HbR(Qil>ndh+*$o{F)=csa0;c!;*a6)D>PgeSplp3^NJOu02keUT;aJ z4WK0ev6nN;%6EuIu2!2`g{83XpYT62|A@`ZfBgh{aF02@f*M)IB&=541C^eN`TwpvVYLD< zTL59C)j}u4@Bw|#7eKnsfqV_pb%V8;JGRjo_Gw*TFE{tV_ETaY*zs`H~Lopj(f3vNP*Ma%cKMz>sV9C`>9b~bYEW*vXKtx$hQ_GHkl?0D#Xo*==~Bn=?IGb+Z1^qin;r zOl0QhD{B7lwnY9IsCv6&enJ`it}U$2g)XSQmK)`I{tRl9^ZnX@dA)WBo14GojaO57 zpL34Lpi#UUkl2hxdkh3ZayfL_SHF<-zUwLn*8>0^RjN}s;^(a}E0)aGFBpx`)u~Kr zC5epCeaf$pw!Hz_$CFUQQ+}L>ArP-H9}2m4vOq7tZ+k|^io4+1r(%JRgMRJ6ZRxC@ znYs6*&uE75pGW-98q?l2XrI@gFJQoOw{r!Zbzawb>-{Wp$Duk2v#^vsN%DJr^2FL- zE=)^)=zj2q%|MzXi2c6o3VO>@%xZLOV*%=O&of0NiYB|C>K89Nw|+vE|20R#06w32 zVRXP-QU;Nf(;d{{fO=TQPipr2lr-?@@vn*PcK=wrO{&W)P&qr{Vf_qq^=(mjEs8U+ z(>-x#>=6{lUlYvcmf$X{`a9c&V?vF=mVpyvG6a(6@P*-(m5+}ypllepC@btog>U{& zT|r|`HDrf=xq1#kRGx_HDX?Hv@(0jDKYxOVZmKA>ZC#F;?qG7q0m0MCuJamtWs(TRe_=2v+DB4t? z=ZAo^p%D9rD}$zEFy9EAeUTM?)11(u_gS0V(PuaG_@I25*RLH1Owi+m#Pa%=_{t=? z_joXEv&K8)j8XVhXNqxd{XXHViG!wn*U{M?)#GWJ$HS^2Gn3wtBDi7%?IR&ETGnvZ)k@wsmnzG>ugvE03Fl7;67lzzX1AuCL_Ui z84<2gh#Yj!5_;fXi$^x&54Ct?rth*PamgdT%;8E)JH=!_!RppCl|kx;P|{e`<6l#g zY;(J~r^@`3i$z<0=M_QaOAWIp{H3Zjqqz4*3*yYax_t?? zM*~u;Q~!$gy@`A==yEX#B+Z(S^Jr!r6GqLw6y%%V`#i;mBp(bB>BV738=d-24olVh z1wnBWgPS`;MqA}8ueEtv%2DOws~t*CY}!$u?#6jY|G|;?C-Iv)hCTTsXFlqUm_z=!3V%z>q%?qF z-AQmX7nD6Nu}Y0WOX=|9N=(WW8`hCkh%dSPQfhi?H4zB`$;c|o51NdgIfS5gLCgabH7cnTTu$12or^s_H9s8NxykqeNorXn&{(lYy_@X6@tml1Nq8%6OFc2_( z(4ftQg1)xYC84WlV0Jgj^NtCaS+>`0|9daI48TKBivmEW;2c4RPiBRz%qeb2$EuU! z2~bpV=E=(?bZ1|G)rKh!&DngWK+VXOM{I&gJAF>VEGDeer@P>@+^eLnp_N` z%8fguIMxvsln2D4E)BIY0=AJCCT?uu5bDvzBO9(dSiM}5Xrb@fT>M+P$7o4dPT_ zOOrmgEr!;qjbKemY^blW1A+3#$S$w*ZtIty%hK>)r&xCPEXc?KarfDJHZ2G>!?X@~ zZ>wn#h^O?u*M6Op#oDAD&i1;tjppr9-OI~Y1i4?UL2dCB%r5|yv5RbzZ%&@r@{W3& zC|xOUk#ih0x$5|p(a$NUq$U@H_i3l zc$+H6kR?n*L-e~n-gfQj-$*!OBHjeyBZViT{PC{l$GF<*wa`SV+*=9JE>D_dMPud==To%(;+QZwWi%QO6q^ zdW(54-~mSu&O51j`tC!Gi}xXOv+MUFoE{Zmit5G}U6oz@(+F$i-|YVaqz;)1iONRz(<1}lE?f}5H_f!$J=6d>v)F^EBsDFvt&_RF68kwt zBB>Yon6V1vvdL_T$j3}%&4OvR!~`=5h8?Qg$8~DcJcTikE1(KHZo+GOKh&pO;Ljrm zee_gsP7K28Qh;=UH0^CdE^$#VJ?=H%M`0CZdIrlx;v;p?BN|%ccJ-6Qf9N?uXxI%RXLOfsG2z~wZpFP4)O!(FX_Y|RT+mP*5~hTTfg9l~kG#G%Kd?mf zpXXDTId=cB;PYjRru+zDYr{POQhb7(&8MZgL;=ac#RFyfflXpXW6I*XcAUC8zrn`*MbxbMophKWB#$L z?y}dSVNu#S(NwCyz1ysF)=|Gi4GiWgo882q;xe9*eJrs*mRE-rw}I~Qhmf$nEpJ?e zbnWCDr$6SlxR9NS(|uqyD*{Svo&$nEjx;|ww%MwY%}?S(RLwo{8mS!D^7ab>z93 zs^qweEkCbmgl9ZCOR+NEA2QbEcaszv2L(hs8QxnY&0)v~8cY#_Ezox=ZJeK7u@vZh zrHeZ&mEQ7hp{al>Uc|&Vx}x`-crjhPdi^@5$MUNm7=DM_`eM;Lja!%GRW8kvU>(G#A;jJ^vOx%Of_kLEj8W`3=m>H>@F}BK=d{jmC4wB~~*}=!PPb#;t z&u{fXPz<$lbKGDt_q2tSpU3JZa?CxUCnDa#60c5sezSF^Gw3_hAJ8B5k#9I=D=RwF zv8a7GO6tPAN_M(&%d#mWGFbS&y5d%6WYCfpks9oPrV4XM7mX;_w7z#!ERO)$XV(TH z@yq>>{J4i{UJHgYZNgb?kCtud>#~JbOV?FT260OxBn7n@^`-dc;dA(+(U2x;4&5e^ zk`tXhOz}2)Rn*u0!GHGd8AgG z6?0bRZjsPJtQ)3%y6c|qAago#EAgejz{f>DbW8ZeRW^>0a=jg;nd8b6wzFPgQAN!< z)JLn!b7v`5qsttU(Fzxq@%JWeTNU%W724OTfJ6h?NQG-O<*UElfcRr2_JG)=Qj&Fc zB_?#zS1@9z;KMW|e_astmH~}>4Qby*CXQS;p@#E{o02a&dtQ!98VGQkP=+3hixJ_N zrxfNF=Xq5K;9)0Bv$RpzgZ_#IV+~Vu+XeyW!z0B&&;^_J>zlAl^sV&(1r7qJfR*Kcdm_IZ8P#z?_P1!%8FlKQ1M99rDf}A|E4POxk?;R*$LTnL9yJ4iy0! zR$--`x3{j`h}=oqdqjrsxwzsyN#3c)A9nZU^{IfP&x6>(qw`xgD(f;dd@b-6P}}ZC z)|zMX!|E3Pt|6PP)I{Ir&9RDL{6Q>&q(3Sre_)7CUbl?DwtXcb=m(bHA*h{Zyq!|` z)@%M$LT_Eg#;biD5_vYNejUTV|a^RgJPb%OLnG2&-_PrKKuYa_OaK z5Lv8siBr4^Y-?VjHt&^=Qy;!yV?K;fnT{aojMGuU`EulPJkeX-IXv;#l(%J!P3_(^ ziKJPhZKF-=>Es(N#}aDcD_6#w=oSt&2H?#W4JW3FWiB~@ z$=;PqV^yhxy|^vG;h>n_b!3U(a5y<*vOX}?fw3;(gX&=UIMJ&bM@jzf*dNheBJt@d zfBfw_S**nAF#mI!G$u8r_zt#zqv~A5izhs{D6t3z5Ue8i}nW)yy8e7Ex#6GC+XekDos?b45Yw7Nm07 z?P3%SI|%jl@$dKi0t_X5;E?jq{je6n6K-8(Rr+4}BY8feH3G{w7vPq=z3imxk3_%Y zy{Yjm%650^Jhc;Q&#PuHC5l|pe(nofA61H!%j*!vE$?j^^C@F&#H0)7r3r6#L&h)k zwJz~T+(Gd`MUVqZxpsH>m0^AYfv`R9*@hB|qR~(UC2=cKD_0?PN9yOYAg;J5vFvU~ zrDhXgm7eINYm3L4%U@v1s|2hLqrEsToNS-|nAx&OCEKT$m${k^mELs_0AwY9C%rgS)z6-Pf!xsqFdvmrvJlUw_g z=Z5})M~xouJU22Uz`+=^fG@y%rZQe3oO-9_4pq0##mZbXQ?y`&K2B#=dY>BZ+^|bv zb<1%ITgiCxm|rMK%ciF5k_2^@Bw09fmcP5foB1~W6b|;Xd1IPqrO2?qH1pnzyWZlE zkAADByWNk_eORW$yN!S{$PN-JoHdD#9|u_3}iE)xX<_EM^k=qi$vnI39!dxq zoc0%_dK6cW)#(}vOR%@}xOkqUS)sf2cK`sE;xm=vz2gDB;zl?p`+G&iO(1m!^Di9}WM1k5GE zq(Qe_G8W>cCm9VsOG=&w;Z|Wka{Z2x22Dl>1InwdD$F3fVVgI?hq4^94V>F2RE63x zVI2fQRhJSz0lRpuIxME%EKNPOc*y0CoZ;yCGhcv1JP;n)t)L1212SIH-Y(vGjA^tL z*;#|(>Z2k+(#q-+$Uf9^cWD9ON@^aDm}cZ2Jh<+BD6Oe?#mCiBwKQ*VBQ_~0UjIsD z*UR~2tKw8i2^CUqL<6^+O*P-l3x0damu;`UGyMUv`2;uyRdt_nLWP@gYX%h7FS-|X z2^aRRegXb$$0QoC|EpyOT+6AyxkK5weplC?%3R)HOrKu8SpQPO8V@*Q&~vJC%g+KS zMclgw=HyP6;DbvNPX?N#woa{>wBEV2Fnb!r1HU(4^>F%S6^~eZ_oA^VcA%XkN?Tv& zpMQJWm_x77Dd6S1g?;fhVdiC3{I2`x-b0)9)D3_YGffKmf$VpDCuU6!MHFAiZeu{$gV3A7AP^MP|O#yw=D9|)Cp z!*0#rg;}y5nml3ZxAu&SmuN#JwX^zZWu|fhGT8eQ`+ffN{G7&3j0b1;M&Apz#d}j& z8w^fo@8!Pn;Nr2AZ=CVNgG`Ha>H}zkqVB>k^Dl{c(4-DXzNh7VXZa&?-{=PgK3`nR zki>*(wV(dD{baVGT_IEKZBZQW;}8~K)@fex5@WILMJxLCsT)TuXuCv^X_Yt z9&g(9kpyEK$7xn|H(SDa4b}Z#7BX2a!!kn_yEcEr zymw>5z`aOp*;>F$HXGQz*q*h$!qFZ>B}`auTzshEa*qD`Xuws=l(C>6d49t?FEqbr ztwm)jnl-9G}rYlt9h0^5^dX` z3<+t+#Sp8?on@_R#+DfQ!!|Rm1m9KtY05#N9kyl3Lyyojt_Q^_OYK+;vS^K z`?Sj`>*8F+CCk?QIZx8AMFP}LzI}G^^w6|CQ6ezY&4(i{p_Q-7V~KTMdXD;Q#(Ogp z-t6{2m%Pv`X^#?48Y~}`ITof_rkdqf6Teh<{HRo#XCf*r*;4dR{^MzI$&Trwo2n-7 zkb$%~9udOBt%eBzBXh%GTKWqudnTG#8Bx2CP>G|Bf6(l;h$ijj>$G zVT!Ctoxj4(i7$Xsnh2OMM=5=g=uTsG8N&j~#-Z8NTi_5v2`Nz5cr!7LTh%;Yl7V$VW>q3i*gCv(I#fgKSu#4DlsH zU&yBCH+w!iby8`;zP%2vte^;)COqZN;t5oIy^-%$Ywh*8Jd{wdPh^%`qNg$pVQ~3$ zLYL5>kK$iv*WPam2GL%tMPd9^-6l%}yy|XJBUzzyWbNW{(WTLr3A)cIY_Z_M(Z_7i zRvEk8LdLvVMi+Mg?cUF3YQ` z692%4MWc`VGII_I+hL1GeUp8W9N^kp<(&D>ZdcR&wtR-_byEr*8d+yNgWY~{#mZ6* zGOs}P#W;(M#O`a2-d9@&7S7c6(jJhY=1k_v!Mltkki2Dkx4+hBX4@)CMb(rsc)PQ3 zpUylhBH^O%rqcCq6n_Mu0{SE{$#m9dU`|(EE^+Ysw48XgS#3mk*QFh@p|(3 zmRQbn0A65xnrYcwKv$-S$g7xen(3|-kGDoKHdX8{)NPDCk-wALIp8sPV2Q8cbR@og z3m=AQjeuI==NU8Lxs8NRKQjG>K@WExDMEzO_H|^EL;k1gNPh7MtdZCgx@&O%b_Qkj{=#eR2SU!U)~Y_AiVqn@X;y?# zlHUAHsg$x!)y6aE(6|^jk$${aM7bteqrXay%ojeASV$DzG~e5IV}a}re%IrviW!ID zZ#U|tC`D9_J&L=9JvHNf(u+0wG5U$+w+!EuqR-9NW#_#s@s*~FZ&dRZHZC{S35ylY z2V6<5zAb(%IDqicwSsa)z|TEHNsssDwGJ0vvB3@(bLTs^j|xSJBvpOzxsq9;+`7N< z%z`21Sr~e4<51*77hX09SXYFJZ0T2!{A*AD{Pr-z;+|8i_?LD0&87|6eH^;}hmj>4 z)3={@#gof>h9zaVb;QaTb7_AEw|%4c6M%HOc5dG)d^3E-K5Qs!QXnt^o^lLN$))9( z?AX;2DweXoU$vI8;JqLo5!NNVe{L=JSWBndfMEK@;1Lnv{)bJ=?4EU6SKN{`YT#M1 zj0vt8 zi_VXp+OU1}w)mJ{IVp~{?u%yq$Esgl98=zjS4-RJ35+6Y{u?Ewjb z>5(OdS6~!2h>MM9Rt|l#DsiFqLSdUDxYt#$V6<{+MyWYAgwb!qBN=FEvK$%eD4{?^ zQ6kCL(OJf)vk`W40S2M_Cl!N!#T5YD6OITaFS(Z#(9EhMZm62BX(ic*)wyk&*er4K z?NH3-R$z8pEzLShi6uXXN`>v90gpsyPywC69=yc@QCe zouoQu4;BTED#D0Mn>p>VkEw$TrW_9o(A_Og$)rG!lC^mij$0|VTZ3utRr{Bq(iAWiYoG5uX9}3XP>Q}1*$v3nA>@E!jiYcDn~?P zxBs7&Lob^I0wo=EF$t}Po261@zKP+)SvkK2Z8n2rj{;&dC*+gEkqK+`GMJbS`+b5M z;2CfdpYwitTROE-14T3!8EK4`%EI-JuAOIxNsz897IVSjQNN16AgYcXk{LzNjk-Y*T25T`BIR9SE)Re~WF< z%XKQITZ*N&x2_(EeRn(v{7S&?+ts%p8o`;czI;YT>E1mDp6#oXfuB?LsPTTF zFlaZXWN9e~4q}7ril}h}W3B=O*qwJ%K3l%%!_MJ^VB2%HKU1dB>QQHp4gBT8lzTPrg~Qc=5Kn1M%lWtLT7vYF4UP@wDQtf0Wh5d zM+v*lD;fHLdHUGZU(9jK1!3vOS)`Yao){lE+-h99^^V)jP~d_RHj*-i4ABXf7W$-k~2EAM|U6 z`s+10+sKoQ-PYMnROQ9Mmws*6v7JokbyY1w0Ui+nq{>cSyqe|8=N9q;GJGd1zObS3~aQ7 zW!DmjJ_>=Z4CKKGr$%(SK&nE^sQOLB4Xq&rUS(aIo z0WP~W#bsvKO;lMcAaOYBQS(iOk`V@8UQqtPU6%-P?wzSGhKna+tJ25iHphsG+%iHS z5f(pbk(uFpgX80QJI@slA4gEh*g^RV)+=-C4r`VP9Pp;GWls`v%`@ZR{xlnAsc1`& zSs2``cQJ8|Um&{qD>>E!s6Sr2Jra{NT>afb(?;ULFY!CHZ9+b;*&1h?NVTp@g-tEW z)T;A*^e?o0ja~S>)F#vRv`%I>Ug5IdQF+$Rg_Gs^VfttJ$A2r-h4?60+#ENT`~t`f z@7ZkO{|u2Yo>eTcE=}$4T=uhUbkvq24K9Z=1Rx?=mL?xyxZJqxu6&v3i9qNQn~$^O zzqI_V>F0~}^Kc0s)LEF4ih`DeT@tLC4P;+HHPoXA3z^79Mbc@jv|4UOSZUXFzZKRh z+9+N8f>p>lDXXXB?-Pmmz|%~VKeFONOAuFCYQazBv)xrJss2~A&XPm)%K2}of*VD8 zX%i)^XFN!5Gi%LJ&j>ovt)=UkjW2wt8H=U^{t6={aC2Tv=c;F*=wqoi8rRSCzUDw} z4i!PkrCGyf296_j5yk6*yWdz9<+oY&ujp|$E5~I8cLpWrPNS0{^>;WEUQAwgLkmOl zre9PY5+&h@Ne%Rz!^Wm@MSoAh!S|z|%)qV@$?vC*AI`ZMUaLE;f5Eyjn$-)ot!#|H znS}5V|5VK_W}4I%EjkqCk1$p5JSeIT$+CTBqMW?5&gJpLlpa^QDxR@lP;=g~)@iG9>4+!XP{N)% z=oj0}G3ouauLk`$rXUqng}abJrsA~Tw2b4mgw5RXL?bj7o!YvB_6w(uWhiEMs0dn3 z!m#PIVcJlbRakx(m(SThRFy@Ec%1ynQitl#2#(PEtp9oenqsOdHu#qLLrDkBbL$$7 zV){gSyQx^)6{qrhrSHzXCBVrCU73{l&W&?5tQpw@e2Nxl6z0!;dRZ@fH`+kaDPw9s zyYFjm$22tuJWF{(TSsI64|{JO4`uuQ4-cuh+b}n6s3t~2LiVjRvJFCZks)F%+1Ej( z3_|u{Fh$5RCNap`Vo8><&e->TNsOhLa6i}m-BR84`Tg;HUa#l-ynfdoT-SM>=W)Ex zW3F>M&f_@FB(4S~uWHMB*VbGPbgk!FZRD&gZO)_smiQ?!sbDm~xay1O$YI%cF>Lz@ zMbjlzcDtcvnwH^6`Pe!uOCG?0Wll~c-C^_QDbf6=X3zUv%fn2ae4EWTOLcT=pDvXI zan^-_ZOySdm0O!L@e!sg#-t4wtb>a~y&#LXyo&^=ce?w{e*^peKSNU1)YIdEjc+HK zIGPNKto;M2v}H?9z4K|f-V!Cz)iFGT6&Rb}cWeQ()|vOy^=#n@hLH0L)s~`o3{Tp| z*C#zTpEAP$>R|A>d!mZV&m79_t}f4buCvT;nj59U`(i1{B>&qROO+^G<7?9o@Rq4{ zz?bbyL)201`M+CslR;GOXFc)t}mA??t}s6q$`19`nL(brDnIwiU-J^=A=e@AOJ;cvE$A}OF%Ir zB^l_6UwZc1I++9WpM6damsvK@M%hlsoIB&< z;xacbaUAeM<}3r$bIU#?Ufzm@;XbE;bezS)Yg~l=Elz&^V#3$`b-sDSUE;_3zl_RKYrnj^?P$ z(&`wuk4k-y@!L~+lJ4l9fpus0*M<?QFwW7!#DrfidCt3pN=9fbwy?4q>DwF z{?yYjg`so;rjGA+!N;a_r=+g=p-ugj9a|=NeRki5l1t}STpPBf{-&kpYBCWnzPBcn zDtwt|**WnW!td`$9jomJP&%r3;*w@}UE^A`(T4@FXrz(KhN9q%Vu195G3K6}%Paa9 zZl&l`xPzkB;qQnc{7_Ws45o)U){X9E#=No+;1gSFHUu{w1LLe-t&R2g1gtRxhGtBy zm4L-QQ~@T7SU&}Si)K&iNJ@JcAM*Cv#$O=4FO56m?-*psUF?=6Hb`kS&`1b2P+o>FIwaCa>336b3W3- z&IgZ)3WO6Ma6y>p^k^xO$6*Ji*1lHzF6o?#+b7$DoL=)o9bcIE?DKLW1j@Uy{>jMF z1ZwMK)8FSd+*fVJlT$FGC_ckrGtxo)isPmp6qPK==ic8{qiRMzmJq)YFme{u+)lmz zKhVCvVW?_+5p4HWY43D(hAxk=;Y*I^=4%h*<={-Q=9+f-Rs*iY!kp28Z!?YRx z{DBV7@<7p?U}%R7s#b-5q|U<=i&?qd+2mNjsupzIyIL|IyLuhkM9g-I-K>EF9n@w~(`t<@C|Mi6y?ZarZYkhy(U#nd_-u>K-7Ry*X)2C-FQSbo-lsTL zd{+?Y7h&21i)PNJaI3Vt6Fykx0b zv;T-xrsQVf#ujy^n;|x_^k!%;HNg9(Xso{J6n*n}+whH4hyG;6(ACyEJZYuD|0pyd z(Qk9G{)$VMQEGLp3X-D_g&m_uaO4#!==9Kzq|vH+7mV+LFW}~P+K#GDyba|*lrQ`l zfyiUM7l&-8OK>fxzX}dxI}NHWdpfY-A-i~&{S`$xu}>H|O`7Xp{~E|Y)f=3QsjjXj z^ePH|s851l&7$)+<=bdW7v$G&ZgoCQkGP8d6*L`_=5eOQnrf;i`MzM3>-JIa72}Y4 zheFHesFM@sZKuQ%QsA(}#hQ#U2}OD7yZO>Z^=C}$0BA#%gT4}eIuh4Gu;8ZoVJElQ zwHk-Zv9`_$tFJ7mNj*5bUKiSt*jpiFeKDIgXT{~ZfZBuF#&9F0z8&x9C3T{LcbZ26 zx^#h7;ePp7==%NEE*?N>T4t6Ev1Hlv)RqQBB)>psNS?Cj+tN1)$1F&9v5883s)?~U zt3Lfc;*bH}KB~yUkoj_QQ%T>amkwJgkBy&QWC(b@F}##&@2U954Y-D;X$R7RCgN|| zvC!w4c0^`{bQ*kC($bp(IE{0W4V`>21)JPQTBaA>jSK2LPUgU2NnybFJ#3vwCDhPS*mLjYAf2u%iGo02$o|X&i;PzhZBxXy9wx~eQ zu(DvkdRe5EP?{$)Ic)pRDiwD)n$~g>T3LbBf^sL;VxxN1Vh3k2kTK^w676$6)(Bmj z(=m+F8Q;PqDDi@eUzM6YO9lVD5;uzz?Yb<|@yosi#4@U>#= zyU&>M+ZsJdvz9uvBWFWfql(xZo(ZM>1?oKeuw(QsTr?ar!3C&!3s{5MiN#+PWT50i zfovtEI3BQ*2eS$aoTOzDd4rZzZCbi2`o+I5p#T& z;8#gY=gt7^JD{5jy|Qlk}S8aH2VN1FePWYw_Pwa??gx{a*b#?V|;uo7_AXLX<$38$e%ahdgW^05Q4&}s7ZlDBb zgHBA2i-@1E-$|IIweBfkY+(^@UL=j+5`wF81P>-*o%liq4vc4o~VM+1*s>J~=03pM@n;@?c$ct~hKlu^nN9?vBtw9WCpvTc=#;q~?vN<7hzYD%o4;w{eCo>N z>Uj1Ea(s%l3Ic(EurY!#@Hbl7$}S0J{Z3Lf@S136O4h)_j!|m|J4@`&<87${MNewb zrPgc&l802XMNx6%C{W3NxfG~53H(wbfVBQ&Vf(Yx_AT%{XPNg8JR=o89uBa5A+}kH`dSlu|&mgWc@DMHPO92YtT4aaw7EWQT=ncDRalmjIWsnvfx3GXz zmx%D%wrpPMKl7*ZP0nUu=mPrxyFW|mzfxHG=_RZz1KXA}<%>iYbIeKyH=!f&&}c%2 zz?Bkund(ZLH_t&0r|3^Uc zD)^V+_zR|AUKSo&F7JEQ^Xjf>zak-N!Uue=Q4iRZGaT7MUI`^>yn}$?pK7o@bl<~B z%NfAmNg(QsX~vU+dMC5X599n=AaleRdm`R3S^TR)rFMkXC~f9w6tI&mMttQbrR3Zm zP^r&BOfujhlm$@Ii3#XNK|}vxoycGX|C>y|1P1c2u#i1{e5bl~4j(JbCcR)f(6Qkx zHFM0?RuHCAW~o1`sLsMd zoYjU?n&K#*23$&a6y;_t&M`vOwc)SJMqFlU3|_#7#MzGTkUp*)O+cIvDI2Iwuwoq2 z5qv!*q)dD&KtwdIy3#@=qK>RQcIny z8=sICjUi8zb)mtXph8#V$SDC729?^#NH*STTTbJMQu`B1;Q?dFvqrR z=eTy*_S`8+7rAgsQOGmGI!_SHq-@CNOI)_g>6%+x+%z-i(N+!Xxxv8u(bnBxE*cLg zYo^nWy;>2=le72>G`VxKMc_|Na#WI1tLr`IB=z4!Q)k*VkB(xHJB9x|EY~V-xi=#r zL5}cj3w%@boF0U~Yb_{qHR$|`RsExvn1VVeM9*gSt8?WLiU)88r&OtrF* z<_&N@;`Ixwy1&KAAXBN$&kF9@J~Bhpa(mgD`$uD^AMVk@)H!Q(SnD84qp8An=fOOC6H{i`Gi}rC!APtgnT*fg0OsAt01q zQyKjeWva^JoJ9wuFnV6;Vz;U7%YsfwP2t3hsWAqyfswlYo1gSAP(J3xQ92LhF|lZ> z%6GNi-ob1Rb>w}Ou4ITI(Ic(MXplOsstNJ1s z^)f5lNd~Ya`Lyx1x~Fk+dQCpP^a(~8mJRug9Z<^8rEe5Z$$yg3R)fQIRMw*t>|dFf zYB_0;xf_K)-Z8fIS+cFyXGSZ^AmOqCSe8|6x6vB zNnOUy=X0jUi2n&-a!ImxgR9tv*XhaD@*gs0TBO=P{WT^~^YC5cH~u@74fCru^@({9 z4xP@5kq2KO2It`7o0jVJt5M!9R5B5+g#dzbwV3gjxmT?D1AZ3ASLp(w&C#ueRBQ6= zI_7)`BQFad*mHSt7!}dC+9uc*VWBU9_O#Ta8K%#FBm%mM;iVm?;Qg7;r+ylsG>p$ugXOXEV&c_B#ck?f z%$G56U~?$`1M!tzFKW-6d1$ML^tg*aJYd7vUSpQ~AuI(5Vyth~6EIM>hhMk#)^8-j zv{rMIm%*6;*lE2_>?W=lA|1|*Y>t3C5D^eW*n@#V@ATRPs-la;XGE@v#8V1_}rKhCPGx zO#J7|M?d)$g1MK~Qw-a~GfU`64Ki?dPPB?G#HD>Rts!*!1eb-M)frYIGb3+EFf8T_ zn4J-!f0PY5!k_87_Nf+4Ym0r}tM%e>t=7O6$ud?g+zR`m#@smWTs>_vKGKkB8rCosRt>NEMSuWrehFg zUNxo}t=Qn|%Zll$c>hB4yIDIX!g z6#P0!S(7=2capd|wYj}#RyGK?GAx_;s#$O!Wsxh1Eo!si>IotmyRn56GzqpHo)r!L z4Fr~vue9^QYba%zz4I95>shE;limP1OSR+>7!-`c*$q4TIPcMqDqrc0VZS0O5r5_) zhCvjF^Zg{g9~jf2UDfn`M^aV-|4?=j=OkL0Z76Bda1xH;?*ur|1-F-c(=~ztqoKe4 zUSR$D0#lGBKs0aM{1@q|1&97zw82rjQitN%kBG1mI3$A~I#9F>EPhC9$6T+SzX3QH zb*k|(#fX9-;5#f@gt!Z4E|oi^o75Vmn`&WAcaqN>95EOOs5Oe?rJN8jR)K7jM4GPe z-RxiT`85P!xuxJ0Z35ECU*O?Uk<8ZUTrqGAU$-=yP}HB)dMd~qGTFOcJw5ZDT4Q$e z*q`-k1<=z6dcYv|%P~^LP~^xE1$A=d>F8?_4#;hf47Eoy3n@zJqb+DWA|VWKErM)y zyTsu1iTmR35HN0Ei>L<>%pdb*u6T_ohV^h3zF#=$5;kiO#H9$dFa_PiNd96#<$ zI=NT09`YwdpGlqlK0;UQRr7ReGV110Z!%)k_kw$?UT3mU?s3I?gIfbBsl`!9HNc|4 zcA0iqIpbDZA&TpFC_u;ko#k4AGXv(eFNo=h8Hh3id!{aVTNBgiH-sWobQxsI6iyj* z`n@hZdxh{e`T*AzweuL>;N%D`ychiO@TZ94#k;FjmQwl8`J{k(R*Uw?g6UtNw1p)P zL5@5Kq~_dc=ruM@ij&tA`Nbg+PQk`nUDHS1cUDV%RP! zbEXbdk66~OEyzUKX^z#Kl0~gcl$qnzlCd_He5-z&DAk+GRCliQP@P8*gdHz?g09TY z!``ihlx$4{jue9%hTaM@t-j&GI4{z#yqy0FpTfJo)X}G-RjVraKi(k_!o4W* zbQ=GChzK7LBM&U&<&UQrRX(R~Z9K3Xqt@FqrwAX2=(m$1*wxN?e-%-Z`FN+q;-ITZ z81^Idma<4UN;P$~O6v8cj3N%S&8+{CggZ}}7A3b@FaK61I%NeD7eQFBwB@}8Metm` zvilH)k*mvrx2XgL&g7Jj$UicW0yR#h3nT3=C~Dl{e4SB>Z@?E(GG}WXWqxzXYe0^+ zU9ogZBENItA{nA9WxDqXOnYEH9fzIGreN?l3IxGOPdOLN{3z3+cj~Vd3IBoeNRVZn zRmdB&OgiCEW`vg0om7Nv+@X;kV3p20?h1u|U=Hsuz9B@UE=s=Mz}`%%R5VYd&NPSF z*G9JzdK}~xwJbyTvgsMZVlz#P6~`>>d+gDtr+yEPkm|%Q<6E*!LIlr2cne|yLn2J<-Z(Mil#k4Uaktl6mydo} zSn@v~GEu-$qv1ra*@Jz-w6tj+@cX&~fr>QSXt(xAp?W`?V&U0_jexl_5Ra9=tF%^v zHWLfxjJ0CE4}-82n6QV$vB%o-mcZ3=S!$?rC;tMauhyJ2y{=3|pRZa=FWhqz#5uJ1 z`h4*3t9aZ$(98^Ubap4X&8-Iuvk)=XK=P8kajLU4m*hJ|bxphs=ThDwd7PTN&TDJ`%_n23kvf8AVc%WC{$o*aJr46Pn1P!5-RWo$E~wKh^=Oo1*em1d z*_b~aD@L+tJD%?s<{0%4z|g^Pz(p6Kr^W!d|CDO|1?rGWIebJ@Q!C?@!HpZ09Azja z(j^Iy&0LkN+V86dVjPl+(==v~`P(8raT?NDs4*0EAsG{fH}T%4=;w|}llBUV#b+Bp zU~rX;+dx;(9s=n~@?JZ%bKyW5rOy}r5P>MZG29ZEp40T^%P6DUTECOyzu*bdUhZqN z4hGAe%JTPdVQ(sVs~J(RmY{$gdFpYZMlQ5_07A!6nn2xq6->U-;7u}drb3l#enbgoKjnBIDYo{c7t%;!6e@S+0QX%m0d{R)q*O<0Vtw0BWtm zm4`3e=@Oh=Y|{?`4oFg|-r4>Oq*YCtUUeXzK)HlAg>oZ`#U)9&fuezBNt2DEomBJX z2S$&C#XagyLm=pfGhCKIrPDHw%pjbq=5zAXf5n_Z&M8{yK8O!nKtGI)rMC1V5F)@# zjMy$#2z>(6^Q?)81*l!2I8C*yL0gkgJK)$b^EHc&?_V` z4Nd@Dv#M`{O(1M0VeuM3G<2(r8qb;WY9TfyMGajA-7BUkA#X%p4!hS#|GpxBzvb`i zw3moy2~H|2Ta>5@jVevxi-{^NX?Y{elqC@sCU}6kJVTf%19>O!96IE9wqTrVP0vb7 z-WvuPKQ8ls{b7I@#A`{U(aVf>J$)PL&icB>$tf+}VI_5@_0vV-fL1SG7!+H?obrxC z%L<5r|9FK5|EXE)1M&=rrIg~@_T^to2*fZq@?~u;7yx83VMTJUQA>`v9-(aOBoJ1@ ziGkJI+Nz~6vZB!;@0i2mQwJ;(2Sa5haxXHt7^xR@{=#nkJF)S*m$ZLz0D*&otjVL( z9++p6=n#{Osd0?9E|HuOSAY}0`1D>W^X)yu>hE``usE(05e#K?TSl2TuzpA9pC1ll zM|6~CUNBk-RSuJa@Y9~g=#;vaR@>W>d0jRtju=BHQ{bd7q5Di{PoT_NXRw$V`2>cG z;5he}?LNQJPJCmQx-I1Ha~cFz1onlDUa!txsxiLbbqeEN;XukUn=#Q{4{hy$EQB&;7ci_o4bs zmlHHHbQP<~9i#2D0SUpNqy2!fh(qwN$$fhR{xm0Fwev?X160Rwz`ty5VZCDH!nyc& zoewjdqMQyG+M;2wcf-+MjZSeoFFF=hSV2r9_C&J%{5KN+arX=TJ$P|;a3tj=6a+Z) zeJ%61)HbFr1Yj<9zKqCzZd!a%!|nFwBI@Xxk0;&9E`sYc2+q<}){89Z`GNW`9{IaE zCp(b#AP}Q%Uv+wGZEeqDJ+?V4nu)^x1s^T)io8J{c9iy@*B(iaVFd$i1nWQBwE+1A zwI7lhq-Lv!4;?-i3m z{qKbejtNp#yj0M`SQ=9pT zXxSe!{_>Hj*jd4NJrMria|W&LlT|fNMp37KS>neRsg*Y$=^V~HSSTi#Itn zy}BBauHYJRC{=e!#Cm9$(jC+7T-!2BmJxqJ|?fe=?K( zR`mbpcfj=M-+nO!B$mLLMLVDVBPRyOO=VF%2KG|(*hj$R^dBw;tWZPsEnpx`rHQtk z_=gN2rWvzCe;4IfyhCpIhYWk5_LQd}c>n5im0KWwL^tIh@*H>9X98iJmS;*d&cTK* zS#@{SgN{q)=U@LhulVG9^j^BzYhgle6t7yyGr5x{7Z=>`z5$p0lSBD!QYiCr|C3Hy zYD^)ZJiKP%zPko!)T*#%v_p?3MqxiFOsg!*N5&UveGE0)2= zf**10b4+RH$9HygKf$m*fZj}{LG?APD2?Ha>@i^Hs9Mf@MOclW%1jF}5J!4CFd7VI zOUqM_jfupSb}g!-qaD%H<8IiQv;0?+TMm43u>P@NTB{?%f503K*I<3TeY3z+e(QP| zYYdB--LoS3A!!XTyYuZat3%sAZTnfeXzU|p&NzHv19#4jPtMixUBq1B4LL3=x6(w3 zR}=1-qCRj-dGkFxhB%}Km^%>1!@l?HNv;~3%WR?-m0>OHu<3=FX?wMO6QR1rs2|$H z9!Zome{JAh=B9qqyAF8%MOcP~~)ewY|mQ`$ypDd9oVv&XMgz8ceXFY8Ki-*bqTDsOslIG=XvZvT$`9wZO@aDDE()c1-;0!huUBm_`kD8BM_`|0zYRT- z+g_QQe)n{4&nH)3pNtpxE&}_VED(ggRO-;uhy>n5QzT=|Di+)^YAx>M{vCcsKd!N% zrf0lo+viuVqg&@hX(6Z*+X}uD3GFn?3r}HdnAD2%ac8ZkJKR9gr5R_ap zy=GEX&5QMO-Y0MfGai+6={s7axa&;|()!GDm@lxagaPV_-Z@qbU_(FB)!y}9?FvhB zO~cMcpb=QP_+kk@4nMG3F_);LKJy}8=W5Igo57jj-PB~O$`w>)<2Yoo7GA7T%c{-e z+0yaH<3iU=%>cU>docbP^2htgzF`P<_X=!<8*m2trsKHDWMTlCA%`dkmi8 z=0xlCGk>#-7D%{b`aOTaSZjQQU+r172h~M00^vnW%k&giiCnfDrU#m#s z1#rAmt=W_W_(BPlS5-4Q+v(cN-IQd_>FG)O9N95EIMTc_H(*LF9?c`hxu#GfBKz2t zO_WucQD2<5WkAU#Yf6SA*_1rE9(a|p@%e-m9JU@%$9>_>Zfcg^=}vwYeA$p{^C~88 zp3V6BC_&NV+qcW5?K#i}Y^?X#<6BxxX z@1n-gIdf(Hwd*;vF5eRgee3BumfW`@YptHu;gKBZ@D{_Qeimq)s(^)SgYTmNaYKaMize#rFnYmm~b$3n&?b zzm=>NhqQ2V+_@iMdmKI>lHEBjgwuTAcrgt1GD3Tt;>RKE-Ee@;fC1hjr6OAw{g{P;<*X@`*wnN z(c6>RCW#W^v^_Jwm3>TdTlGnO)QiFq;)L5AVBgL0T&0YvuVVqiq@`7*RCsDF#Wt19 ztQo8%z=wxK)WgJr)aH~)z4T<{+yYW=S{O!>rcGopP{OE52`2W7t07^ z#sz*OeBo@pv%UpBVP)0Mc^V5 ziWatR^R96XGRY1HS#a~*-BtTTOJG9fGjZeoMPGEtR~J5fI68{*Vii-QC#XR#?n=MG zf1!J<9*4NC6(IAfp^)A0MyD1*vN|VgPM)XWmDblyYTeWtPoQnF)3T(~=oCD$qKH9<*!-dbvjgtY+9 z4h-C|JPMX74YD79q4Rv~-C9BZi;p0Dx?Em=+JomH@COmB=L z;_WrPd{b7OnyqH7r#r3A9avj+=ev(jiwYRvBS@A8nN8AIszy4j$2bS)R?z85r!Eh6 zeWMH7uY>U_U4tSm$cJGDor}H!9(hvRwQ-d9###32i>vE>QfqW+TE=xuaPnMRv}W|R zHj1o^f1hKT1?fS71%FEmcs(jj0fjexFWB>d(sfJn;-D z5x3ku40@EDD|bFPmuh>%#$i8q=l`}^G0dW$Z>mdMQq!L z{`z{%$nJ5I`-1)K39?~&PV?y1UfSfqC|8A# zWbPZ98m5#CZ!e}RaF*(5wM_HP`RGa{U2Q$tzYFT#GFs!y2_ZnV5h0fSJ}o{AY<=rI zM*Gf_OB%76jgggWKHYih%C`=TaKpqriJruv8Z8%OKV@(or&xy_De79yO>a8{<90dc ze5>x81VDXAp2kq5po{wCt&wG4*@*&$0N1;EC65gjS6$8Wqf$I#5w;?fg~c`kf3#LO z;@*BuPG?8Ek*WSoA|BgAYZKC1#`S%%DXo-O=5`T0_?ozjHaGVP;l(eUn8>AC1u!?| zFlM}rel(@)tp>TAKpwxg?iZi|=-ubGorj)A>hQhkd2vOd4duyQBikn;*))PX14i0z zDxTuP&uuL}qfn0}4el1Qr*DFzUm-W1ZD`8<$$CwYMnd?qzY9O%k)XN9?83Y(@qI2+ zR?*twLCAHyuf-k%_7ELap1_-a#zd!Bs$&{(3?*rvxazm8^fY;$$L&Mfj*{-&AQG42Ti_Q}i3mwv+ws$t`*WQ8u+KN~?0H6#drgEQd5Rlg!b@8! zuF4A6NJq@L3^6nKdJj*(01P_WY|wA^eH1bGxLE(N`x^sMHpwM-u{(|KkuoCoO*o40 z1zrDjdb){-dP75m8M5tr`Z+BMP%d#%OOuK7w(G-^`h#GvD{Nrj4zE3FB-SM}N(2rlcTk>tq zkj7X=$n_tivP%yPQv-J5RGWKZ2Lc=4*Hs#?@kT{sWOqA5?>CC~t8vh^G@3wgD>f$J zA&GR6#BK`Snp<|Tz+}@)Iyb&~K26BSb@V+DYEjZWSt3wBl2y2mJToy|6QyZ`om#F6 z&>|Eq$xF{oPd9PvocHj3^9??@bDF0zzQka@%7L4w3{%MG0=OpN38oF_H>A7!dk4m$ zwkCnElMc)KeaVj!aZIM#H0dPXdBz?!TpA@b#AC4RTVt_f`KL{3Bk^;chm?#v^8!sg zr{x<1P&!U%gX>`i!C=C`v%&&V-c#FA_2`g;ifb3KF0yJMRU*r_ks|znN9fo>m{b!^ z5}rT{m1I5pIzQ`yh*6CHB=+{FB_Fg+O_0-=mE6EqY&XClt93VnoO2soA^E@ubf#;b zcBhdoDASi4TtL4GatNyX3)h^KVQ0Z?R82ti@$?lwn3U(1!bI7Yb&kg?qsjT`{tY~zd zLPp$7!?*om>sZ!B{~~I{bMQ`Bb5We|dZ2DNf+fA7R*925BSsUEYM!-_Ek-VWbz#|W zq$Gry_tpPSNS?l`xgGd~Xrl?_;n1KD6fS_pRjRCChgxGazU?hG$R#*AIeBBJrd-h6 z_-X2Ex7$tAf&Q%c#4@8J7t&5R*wm;{{p5LI)uq)r4qiWK)YPafY@r^Hm-&%fT;>9%z3q z`AAc;Km@;Pw8CR!k8DJj_$O3=)pJAMQuCGR8viI}VjB0&bhvkU{$$By-+GND8at5N zzl!$Lz9x{b=pySWv#SP&agy8@MlQ^E_0YInkNA#@8WR035K@7roGoP+GLKv<2W*n2 z=sKR}H_(M@}|D;V;tKtOZ`prYuUI%}x z!eN}mNuJ^eq|vEo<9F&52=V0Sx|DSbBO`7^`N~E)kyJ~3SMc#RU}-li#uiRHKNhg5 zeddo=m;SfS+0HpUn@y6nM%@A9VX5hrk$o<2_?Sb7ZDargooLxN;v+BJSdzL_6O~^tjU7lR^M8ueDk1&+s zg6@HNV{7dQrIhKrMal?fmCBT=Es-nlvBO;`oPGnRmn=x`aIVz}qD_0jxQ=@_2n2CFQ=lRFDd z^GIN3v(z_wjl82Bm;q}#2YUj(RQkz(Rh0B}OvGOsUR_g0%CM!Zn{w%)RQExOig2xI z+PkMr^X?F@ZJiSz8V5Qj^La9jLQM^e*ACmAG9-rURcmP-pPQJ!dk3Q~&I)Et#-t88 zDWLeZD~^lUmulgR-c!}Fwwo4b4-)39CeJOb$TZDTFMk8QUsIKfR6c5+wY@*~p?9^; z<19fV?=G5_BL_N*yKS#B`v zv3GuhAz;E3nGYqSzmW#@_Ri$=xj}E_yj(Fr`;$)MHOOM9BW+Y-K1UW3_pI(QmY!B4 ziSJ@9VLKzvMkG&7Pu0;6OUm8GYHe&w=0xRfl;w4dwN#%HKWNAu4RM*@t>*%hCp9Z8 zGohzcnx+MLLY1(Mrtdp814>Jep|k93olUp*kqL@mgJ(=1Z!OsLq~E?-Eo^47fxW%p z7lgCVCbupJ9D>^Q&|Zg^UHlw{ySNKBw}naEz+L@6TW14848{Ekhr$!``P<7=BOS;^ zz-)w)US@6R$Mu+vDeg|mNz?`BhFa;RTQkEORjnJ%#5VMKT+oo$Y#NI>ZIxvT}Yt z#nijpRq_KCRQsC15%-yjxvI&{nzN?2=gTDwg?m>%_ZRN^v_bs2A2AE-z8fljCwaVH z^lmDGfv~V&;lnOw+z29Gu(N+md0^ng(ts)EEL5{i;oxfzPlU*Ddk1mJ9t-&Y1!{&w zSPaz=qLOXbcAODspJg_N>R@)sW>0P_x~zP(7g*78s903$lRArx`{?@*#1Ntc*)-Z3 z0R5GDDS6QPu@}syDUK(RS*l_XezeIxxq1^NnG+7{@2eg`lau|lovUD~Pgmjr1;B3V zNNBu*SGs^eZ{r)7?dI@Nix^Xq*?@19SB-RDk*TWoRti|g{dR>w#NPAb8$_C@V`(z> zcJJEBojpui_#jp6$B%A)(K6>7oR-hIAjfu51}(0P6)$dWXP@8TIZTD zHf)J|l<*K~+Mr$Kb{B}$HaM?(mYq@^=ZIB^8VTum2q;5bN6#fan;C_v`ZpnSGIx*I zP=>DVx8NligLtDHZ0kFoWs>WHsi0yde%j-6Cj>$ar$}i^Djd>ys;yN$*k9GJ(S?MM zKm%(tK5xa^_9--CC*h?Er_v#S@qN6c$af0M^jfZ=^}z!Pgu>Mm9c*QvD@9o zoERlO;I?9%P4aJ4E*kpeGiW^ivh3w2T8+g)1Hk7e{2fFW%o2~ck8j0e?TRTYRcI5b zdB0i~SOsd<4Ik#!a>ax^VNMHso6~_@;;0uH@&l}F{p$$ux%BooX zQFj>)SlEgSU~QT!2M1TUzQnhD@{b1RLUl~0H8R`3Y`f>S1n(v}J2#L-aGF{poscCPBc zNj}+4k3Y9rWI13c`z%jm<_ghJ%%7lCS71mqQ!1MK>}v`{WPrh$*`fM^w*5Sy|~fyGb5ylw(5mKAR$%Ez9NtEFL$-k$o3ELsZ?HktZd0Yb^ll z$l=ktYQUx!Y75~hZlR8Iqys9P!My7Q%6xL%+K+a~_Q|5RI@@Qcu5~OW^>{4VvT;0F zj9#>Se2)sB=V5?LFHU|U=Rla!kX8P zfz@T$V3JLpD(64bgal5eN0R_m;Id^)OyvXS@38E*OCt!SPeg>=m+P87AEK?J-Zy&J zwO$v@1bd>E%t9=ymUIzlGvT+)dwOtoKbR^@vIUFk4X(={pc0aPMSy>(Un=J+$pwRn(ts ztWATXR0=@RLF=WQe!kr)i@bOaGoA2o~^^VtR>$o&^ z$LB?jXnu6~;}ONKI8GBwA~!OJnA*C#toa>(J*a&fdiz}SthqWm{5>I}xsK2=^#h&b z6k36c&-#~z-pgUR2fEA)wE|-+%bI6CLeAVAS{8hGaGMAA1LM74 zxeBf-6?aXUod4L7L@a6>9}RgDc1BcG;GX1yr(h%H3@c{w4y_mC?CADG#X+)|OYLP( zKYbR3KNEWdxIROZ_%&VvC+soD$e|P{QaCX*8osndtA^a<@;m(l-~HEo*W;|o(#@CE z!A0!@M*5mYJks}Fu+~WTvzBRjU^e4K3ljf5-N5X`??#Zn$=Hp|AY!`KDhBJGz|q&p zWXIPAkn)!cNL?{QX1E!FFkN)Ie|YM<&g1W52Rtv{c~Cqn;TvKK>0-Li`h*bz;Y>|C z?^~^VbPhT9=I=bauRShEO^}XR&7{>Uh`xQ8Itz2i8D@0aa1<_$az)N8VDQ%N+<)IQ zC>)>Zyy{+Ao8MBvo{TAZdWP*{Sa^f;bEl28sWba^M&2Ej$|3F}HYAKC$@_-uM(QkV8>~AG#K@=%Gvmy1 z_izh-)AmzV#)~7TPYkyB`|Gl?gs{Yi-A6E&G^BY|urt4n`D+T{=d?1fN@<>!SdRZO^Ffcc zLp&DKEig7om29#;=b->mrb$v!;%pvEaPl2Tk4aJMzMsk*{cx<@w=Cl{Tm;!=hI^O< zZ5Y->H-;J=X8PnjiC4dmAUEdJmb2v--}<5Gz8d6y5gZu?=aVGLR2U7+O}}K0*Rax3 zICwbmBSc8Uw3kjfWd0MwNL1LdFLGwO%*kTfp(6Z7fpwrK!7&lZnQ^(Xxe*$NxVPWD zb$t3gF9!3IXEK}SpYZuiIe1i*T~=dy%E}Iy5zQ@K5qn6ydA(jM`1=Bk7_X}_gigHr zg$d*aotcH{Ez|SMFb(?;U2{&I`n==ai{@63N53z_LFs3E+g!#POP+TBils8zLeE;7 z=@=bSiyx8PXd~Zv%LI2_t2~a-gS%JI9!Y=vzBChZF`PEExWXdx;9<`qoc#oyt4hYJ zv`nnO31?L4Th(6p!IwR5}m;Q%!@!YjcUSrb8)@EyCvH4Mcc*oedO#EuM z1)q@lA;slubG*Qyawa;mVQE8{IWg%#EpH!J&9H7(#djt5$;(}R7-yRg^_AY{diP=K zvp@IaSYB8#h=KVQl^%$5VoeK@8mSV#6BXp#ATM|MyTYAAT-AyX)srVI+uw?Zwo*DB z;m;Wb^YNjFz)2Tw;&VY)ZXS{7TY?|7u&eY*E-_NAka#uu9fcEENr`d=zxoK*- zVG+^(MGHMxxclpX#pA^`%{E4yLK6M6YnziyXOE=jJolwkdlmveUV)gm3X+!#fMczdMn5vHLQNxf1E1+ zsn9Lu>#g0C4hLPnD=F4vZ%^Hp5gS{rHmv)ooNBSR0!wod_0>SIqVb-zt*V{qoYUy2 zprm#G7msRF*O*J1vBPmjh&Q__Nw`g2dd+>luHTimd@DkeG)qtI5(`s3*}CW@xhzG* z3ovqot5|b}LUynj%-2E_*3H_(rm1oUrXA7yZS0+cchhPgIos?d$mUtqh;Kb0eS#-n z=;Jk9Ji*0zqg3di{cK=<3zgp2^8I&uLM~C-Jsr~g zSx(_ta=OS_vTZUS*Ab=uIh)kds<_h(tUzSpw~f>Ew&zdKGc*^gVh;I6YhJCaw&h6S z_T7LYg(prWFLa1IaqDQZ+0W1jmPKNkr{XBfjy9!cP#ZPS{WHf_D5sR#HmfhyWt>fz zK_zrZ2Q+4&u@YXJk43+mJlZ>}&urigvZhgw>vJp{=}XNlNS~z|GCbMIK0f0O=ss)s zc(#I$T;;;2GZy=z^D$wQ^PailJ~Ms{-+#@Q%HS3*c#e{Rd%bgpU6!W?7y51aa>wau z{?pCk2%EhQrf-zlII{m3qf!@F*$xKkXAYvnR;&9WOeLrukpaoOD|mwD>^szlt{NYu zkK2(J=mBvT(=0j2^OMafw)$tZ%r#AK8$WW~WJZT~aoqL&vQc*C#14O-W4`R*g(0IP zk&Z~Mm%GUA%kI+=>BT!qQ5A7k_T8%%B>9%Q(G>;7LO4xWEcvYd-VOKQW1hK1C4ft4 zJ)Fmzzc?Nuqv+ochtX=%)lDV5y(1M~?;^+$^*V|ZPK?mzQOd_>%ZIw>`(jeQq(zan z9>>|kGj;Y2e*o<(t!aD9#>VkpD@dYeo$>6c__+;4aYXCpd=Y7l*I_Am7rpKQ9{nO? z(;e&XMU|Ym`t?2=%<9&NDpu0$rhrxZQ13w>*N?n4$PaHroj*K8l)KK;f{c10XN?;D z%@cs*tIV={W;WeY{CAU-@QUP7y_TDDx7zB?3Qw_Lr;gwUzf{_4KUM6`wJprLH8eo_ zyqWa{F0a1w_NG#_p+LkbsW$(m`;Z#v;YWhf0e^v<^Xrfst&Y2mard3Hk*8c?^YsJm}rbZ7(tkyJWIQo2jJ5r&dfQaXnYDWyAyk{r4_rMtVkyBpu}yyu?B z=YQ_k`|WN%IKOfK)~sG@uU);HZi5x9w?ZpYT%F4%%7gRsta~e5vD&5vUEr%s=Q6G$ z^BLQ~;Ej%m043vh-)lpz>n-rlpXx%O347Ah^MBEgTrx%qDpj5LD6I~WcS+Z1HY3$f z->o<_s*aa>@6E=^kS_W}2VfY1idM#J+8$FcNJ66ulwTS(AuiO9vIXHel70ZZ-8(Ii}aaz600k< z;I*#44fh91Dy8m+B)`6U*j~m^l1h!Qf5BuZu5oSNqic}WGuu3! zz;F6RfuzJngB$?&BVn?q+@ywS6XstSNwvqEmC;;UKTH&94}qEMvv$!yEPp|qScvHd z@8NKtFAuRtbh+yW$?xBisa00SPba17xc7gx`g#Qm2gKp=7>&FO{MgvI9T9Pz_lWJp zUIb-oIfMHWMcZx+kVcVzKq4sF_H5l>DpYek5o;}mD;4)q--X*`4wOevCzY}ngj+91 zXu^)J#Z(g0cGC&3>Qra_(zYp>pz>my_|>LW&_KG#pQidnp0Cs zG!)11N*LdIY3wE>2CiN_sj4qB6%|6IMhV$w{{aaXLE5%;4=$Sbm?;mciGWo=h8PY zaqYP{4%fZZSGIX&QlNoMiuB~s$>Jgmjv?-W9JOBzUOI6){RR0gf0swPNfi6gz`T}6 zglVanV(W;Y*YMD}0#u55AR&Qv(&xv{vsO(7^W4bP%>v*pSI79*?b*O$+tqE`mH^h5 zUcm87I+#(-D{RP>SC}9DgERo<0lfJqgSD33$E=D@mwPNI1iYE{u4|aw<c7 zk)Z7sy}24T*u5ZotH)0aEGR^VZf8S#g<2bJ&`qJR*x)!DYaTc`(+1G#R2Tb8Sc-L!c6MHpXqNHk;-bhYqWDHV&dVm0$|Tp+Wqe4utm>U7eZ^VM>IXy$rI5YD6RV@G7B#aFZ6zsv z;Tb-h*Wzb2-1ZvDlYu$l)6+8dAa%~B_suB|!!yhBFh@T0cQi&0KihGN!mo|HjV22AM zfl+VXfJ(dLp&FPJVsBDAb}3o%S9*jX=eTMD={nl-%+^|5MyYK6G1Sa;ybRmW9yd`# zB4|B_D)ec);`qqY$~ZBxBR@YYB>(E6GtjQ`(#lUe16%fU+fM0WG4|UuqLp`>wfI4! zmnLbe|7Z^k{F=gkbyI8Q`<$)j8Z&v1i^kUa`0Hecn#ze(zZ&w0Hy8DDi$mpU%7kDr z$H9@8e-!G**1lrA0;CHj-{hsMY{`Vcn=uU>Q-k-M6PL9Af-dEDO|9AKvr{WP$u2>@i3-()$+zs~=m-Dy~k29t=8qKc83`4{$>q8cGy9 zZ6d|G9iw))t$EY`Ygj4wYW#)>6XmrmI8O=wM!V%Sg}JWy){^<0g~xega6QN6rY z_&YU)3DJ63eAp3rqNqUFkf0?8p_!s;?&R!`;YgpIXCRd?p@!m2B;?Xg4e5B7fKVMh7 z7`CMM(--FLc?s7k8<%J1mvL~YBASaQ>~AVML8LGBytS8e357h5E61q@ZYE60rusRZ z{(?@JU*_t$KNamUQm*CN;=Eu~cS_V;TvY#_%kF{6%2RPM5Sw%B>@w+IcQKDTx=FC) zyA(l1@J23%4RBZRU6>Nm<%=w(glVm*Z8txCT9JR)f*|E`;jS#*7t{a1Q(b;5>|SXm zaFTrG{^{H(sg~QqfS12P*Y+C)IUH-(uEpiNLT~U!*AJ;~Pby1L*v?b7$|A|&V2m!7 z^uLBa7#J4`L-T>A7o>%3)2Cjo8PN*fDQ-tYMG4ipJWsi#9!FNbusg{j6URFu3Y~GP zf+t9c1%x!l{6U@9?wrL&N3 zB(hZ0cM+yIF`({;qMO*WNAB|Zxq)aa*6zc(q4`axb4u~oaM-LNL6cC4h=dsliwI;= z)0lfN6{?ucr$u$wdtK_K?yDSd&0hjT0&^>IR^lZao$HMcjQCwO3q%W9EFM^H^|{Nh z?KokNENomkeU?U)eKEP^SnfFdUh51mmys_`8+zz(ct(;oWnsfPQ#C)eb8iST|F^tG z*G|M0^(3t~wH6hX%1792n`~hC*2wAdxww7ZQ6x^K^Uo9oY)ZQ9$wf7nqp2jqj#^8Y zsF47Bxn45H{zy2PzW#d2XA-~rj}`zpxO>k@q=PC49<-LtE`9^t>s_gKbeX4@ue6$s{ z;p$arDkL{Q!NjE4nWdy{2o%b1&JV2>LuOh^(go4tm!W#u*`5krFKHIaV>u7SbL4Lz9Vt*#kc%R@~_)Ep-m*wX5tOO!>k7`P({MMeF#SCdq%og`{g@3o@#|msm1L*c*B=n{ZZyAf?b_gaQuhd=r5bU$JqA0mZB}&lqXsZU zAeRDT#;!?NL?NVD)39P0Uz+!*L@{eldXwZ2k-wqAMMz~!MeJn)sr*Kd#Fs5L%H2s) zDk4j<`W@UzQV(JG*fLO3CZ1VbxN0r`m~a#cgi>`r=!L2?{TR5Xp6_??)PeBF{|ykp z!Nm@Fma52Ezu4h>3e+iSFxp>|& zr%%z-9vvK9c3FnfeQ*31JhV{j!}lf^R!qk~N0%f2^**QF3D$e7SS|I7J3-D&6T`So zKA2;q9?S;N0$`=N%{$VBf+0=+i(9X6)x~_S?ta?LBL4z_>DApvs$|MbSIdxkU{5P+ zE3jiYerVhj1SebmDW962=eN`!8q`7F>Kbd z56YUf*qBae;jlr0!=LwVoX?jh=qRNnEEK$iaX^hLbLte#`b3T3Qg&<4C)C`MzoQ!r zAXVH{v_P8frg*>WqLSRWs7$Cp3ueji4agm~ni^4qwDx^!7D?LhW2ZQd-|*p;1y{2C zS#2{QH}~}4*r1&^j0-HM>rpZ)xbQSHFMeQ4%8gH8ki#qHhRnQnx);QFoIuNk5wqNSko?_(hsh1W_E5KK*!pEsH3yyPZy zZ20^iFFJ5n1m9_aGbvbKglq8I)HbpN%C}3S%CP!HWF=SHn;3uEc6^{H{o(Q-?;%Lh zj@m!&XbY%I``VsYJ6yWeivnV06utS?pR5|DO|0I9>3IF%`MUXcY3{#ZaGEvXUGNF! z7Z{iaCFLe?6+LM4zd50?J+SyPRdmT+(Mb#2#%Qo}&6h#^H2CvBeikl5$${Mb4s!J~ zh>}_Xc3XIvTB|XVIXDJS@>H zHdA3WJ94f-{JQnOPz11=9~mPjq|NLdW}0AY`s1*|u~qeh%u2quid9Fxt#kg5DIeIc zVelPm65e{uXi)d#!45*pl#!2NlaVNkHVCZly)WN<-Kqj2~JB#|Cu3=h|-Da>S$r8Ge zvj6$^uMcpg-`{YVhyWxwGl@Ql{PiFId^Bv+jBPWbL^PWFe}w?+7)V3@Pxt@-)Booj z@P;i5QG5k}Lx3HYf`W*Qf`R~#{O1@I*dz993RWbR56Z7_$SK*r*ola$_~25p+eT%4 zG_e0x-Zq9u%^?QzjYj_LV3;Yc>Q}LL$~k&w^k2uVzyaWd;eG?|cMiyVNKwToRyD@5 zH(SD8117mQ&QN4XhZoJ(^EylZ_;YFIKp&wV51#+I`qQGHjOYt2q)Ssfa^?bx!kHVp z!7N4r!j_o63{QH!uY(sSc%c^>*K$8^Fd0f>)*1HL-e)^})wL8bRL>P?->2e}CKyX^ z?;@lo-4iEUN`Il?E7s;oI$y^i!V}%F+a2X^o^B2IM$e@%eKditQd)4mCHj~gZ@Lp~ zx?1+jC9);V8{J0{8fmL8+;D0}S0->r_0nqUH^8?C-O*|+-x&bU)^zqnK9~KLCqu<^ z6|GD0#4%v9+v7cYp)35=+r(UMyJDDa|?-0tSB zG~JkBL&TCqIZsKkpGreF#@ol zDi#C&J@C2Pa>NF2M)Cwl=nI(9IiT)a@@|e7<2+}-0hMn)o^=s)9%&nd z3cqL|k5~eTt{3Bghu$c?jQb4`nbRzy0b6f2)Y|K6C#W9F71-}DmhngbI+lamEQ>QJ zzDIoRzw)Z#W!O9sQ1trrY4{fnst{wsCn5pL9f(JX6LkJiL;YA8_mTqYTqcIqm(oM#(?@;J~wo+ea#sDCt4E^aM z$_&*1+Ws+Miz{r$7Z-^u4931X6ufCs>;hBrFq>%32z^=C^@g1bNhwANox*F(?JYxw zBKw2o0J+fud%Tc6>@##+B*F{<w*+=%C}s-ao{ec7k%b%KG%@5M-_RM-clFdZ_6#tqY3+a`Sj#$LlW~9AnmN)pe4J=jL9p@6+77uX%S?uciI;cWhJ(9OU z1$N!3e7386#@92vtCts&wi5DQX$BMIcb#rg5Vnl(nelL`jn-_7u&W=G2K1YaT7b~WA>0DbK z5$rZJv`S1p1@|vebg`LY(p7t0XaqdVTv`dePM1Gx!;d;tuzO3vKoZk4Bm|GtZFoa% zX|&U3VBt6eVFu2MC{GA3;yN;sQkEj%94H~~r6~(?sjFWzxFa<%9mcEgHdqe7s5aVf z$=SO!^!4PQ4-{(eBVNMWh|vl5P_#J~nI<5P$7k2+cl3WihnY@hhTRmHMUu0@Q(L6$ z#Y9u4Qu&;Awb#-O_jNLZqk(*C8`@8aKuN+hHWlT_5Y%gYSxK#csp`AhQ$@44&l|-* z7#gYf?8^pb0m>6Fa^HO$%%a8)t)!Rj!PVQCFt&PnieB!sI*M!1I>(_;;rwvg+MECG zyiW+oJquT1S!`SR|4{kX$l9(?H)=0Q(baCm3r<<39!6$4duKX`2*r_(p7?mF_0ZjM zF>jxA5@^`;K`5%^6#8qs&}NPbQdXhBX77Nm5OK^*iNFmU^7t;(5i!{Po>VMyL|GpMhCLGEKMDI4I?jO-mMnc-lnI0w1WY!IT8ccXo56&DYkIgOi?=`G1s@-NAMj7PE8lr3M>HPE;w^&+b?B-BxM_!KzHJZAGL+ z;4zw)ZG40y`#g>@mcef>9X=WZ;fxRf73jH?G%wERXlQ9@YG{_nP2>I7Un~tnF+-`q zXudP&WxWvfQWnciiKGni=IvNt4v75dgf`XzZC}{^D5DKoP_1hSmyQU6`Y1D50xXA_h76XDy4FrJ^E% zCvDCo{c%f9Ww)WqGQZ0AWi=2|tVxqfa-dRPyg-q&HqFZ=eK+SXo$vNt^T`b_%%g!E zCm$*-!(7^b1G;2zAl|l4l7Pn8=ma7{+O2* zgpdn#L!1YOW0-D-I(BqXIB&KjC=njB&~YUPdum7E8q*lk@5Ao6U@r0Ne@#2tWG6qb z63(qq8uZMC_(u9S06jz&#?)|&C)L=RGuENU_Iu&>RZ!wu-d<}Jo66)IA#D2(Bq2~D z+r}W-uM$m@k(Hb4dG(tva^MBu`5%|!0`D7l@{Wy13IZu=6j!RyuXwBIgXYjfgGc)w z{l%j%u5k^g2VH=vyR+8yaieW>Oj{0+L6jC2WJ>(;evIB;eyV#TV`h>{Wg3?(Zd^odH z;uZ)8xG8O#jEwu!0$(mg$7d8BR|`qOICXCcGF$X$R#mM%t!lib9migGF^!az<+(H- zVauLZ;xkHDxv`Vj!nv2-$nA|ec0J#T6{1gYspto>@f62gkGtSJSZ z8#|@gSlxTKT|p_8>s?i~wK-X4Y~L3`eX}01m*F-+H5YGzB9ch~nXMHSDpQNGuKD=` z&fn$?EWcs7@@A8@)@8oGRFP$Znbey%b{|zZvv6Xsbl@HOu8FQcDXufyqoJvsAlPg> zN)l=cmCr^{WX3nWOpa7ht^Y$c?8?wHT3OK_Uwao*6UBBb%@*w;;gTWo>ozQ@C>@YN zNB!CPbzBF|_F<698EV+l1nHb4fsqUDHGVQ#Yhq?sQZF6N0P2M6OH5bg9%6<1DTR+p zX_9-NyqJn_zJ*Sea?wZX*cwj{uFXh4lxqOn`Blu-Aa?vRQ1>As8P zf{zZ`i6E&!REEb%%D9vn?${^k) zmu*yQ@I6AfKN*CB9m^3460nuzwwK8Hq&?p;$yvc3F6~L`u3fjKEV~_k`x{`ZkMG7Yocqkb(5U-;jJAf_NW@ zv~+(ePna+>yCU|hb9llq|N3n1wmKH@4Q?|#&mlaoao#p|mehrIbfEKnW~YD(_iY9) zF~ZU=j^XX+N7;RIA!P0?@T5YwwwqB)7|FwU-Q$d!EYFf*woOBr8p~MW-e}m{)++7O zfG6mE;e-4+`h#@o5!GgERRqmb)+WJq`o2GxpDWVk?MOpQ@{6g$l5D=}FIfWc_kzMG zQ5On6i?np3N<$@AFV2LQ16nCVKc;G)-DN4jjmx=hh9TT)-s9ez5#>n)eA1r#FIBjK zGc_Sle(8J0kKRIxW^O-W`Vx4qXl7${ZB=j4D<4;iZ=VNkSF8Opdk*|AGemXL{jQvh zhS5)Quh9@a7@BJVV-6$@4yRQRC6ng`0WT5~QDCc2({1d1;Ai~LBapP5VLm9|^YB{` z-7;**Bo+8A8Bw_CL?ju#dG_1IJCH)R$clUv6lz`CT5v%3=_l%p&(9YNGJR7Or0*vG zIB5sX{%&=eAOj8hgV&wqctHl5)0{zux(2UF&8o48KNdyW>Ca;+%nP*NtMOOEfgP>K zz=$r!bfbM;q0XD>h*Ii;tq=}NA%ck2?5wyZ?6AeS=0rT=oF-MZB5x;UFcX2(S&G7D zD#81XKc>!(;;d6ZSDYwry|KU8K2ZuESpIMl&bFQ%sg3Tq)C2P{puh!Z^8#~g`)h{W zvbs%}=Jy&1M`{}U;C4>Gqlet^2gA~DrpT}!Ut^r!`aUjH6k@3>iAmemrQl_2eM#?D z;97JwqnFv#;9EfUJTu*t3hBFkCA=^L&EWr=cQ(Khxu@hJx!2wPL6xvzS>g_I3cErt zfqE4y1f*~EiaN5{VR&zXR)2)(j0{qVDEYK42b~Hw_-hp(`t+yeAM)bPfF^GnOScQ? zQdV9K_{D56U6ZS_hmmCF<*sAnFCi|-F1Ys%kb753tqwD^tt8J$9TtBeiTpy(N04ey>Pp*%;M#OYE5SQf{CTI# zlazLcsvOsR_49GHOck;Hi>dbl%8^B1>k11CAHY-7G+|t(nDl&iD7gLC{GE9D%Pxpt z5|+Fu`sVT}OsQ<0wsNk&DZ@Z8 zSV#U8iN-N}J(X2J*^ODVRa_VTqnl-bl=^k974`|D`0L~|$rn8Qgs}L0ma3)c{VF@l zmf%eUwWeroIm$b8;tpGNo&u5j_G5E5Q;xac0HA~77qb=2NL4)i-rWMaj_q7V68f>w-uOc>smL#xUmBnY1!2(-5_Rg%Gx|?`{%(N@*%O0}vlW;y{jv|LQEf{#AfR`L(dS&`*4XzB)p3peB4SMuH9)s%IG;eDQg;9&gDUy22; z0}&Fn35QZjgJcv#Y>6xe37b=DdWvG=doK>P-Eb%!>L{e&Y;c{gXN4_qutWszHzXz{ zF9qgveS;tBlTn3bLI>DS^mzT4@^Kx(!A+sTuRpv~47kUvBzD<#X|d?=!BUWj#8^m4G~QR2i&8tON~rJs+J z6MRyO$629ud6brnonSRQq|OI)Egei`az^J>qvV;+ftGx&{iaAE%p}K<1P&lnn{rofUeh5>-AB zg$4fdxKx=Cg!oI9!DpsaEz-(i$k{dwIl~vwg(#$#qS(Ts>Wq6&?*5Bg5;s%iZT6Z(89B(kvxA+Bue31G>Zz46KF=a_u(`36#5cQsS6cofLZQoG(=- zd(c_f&s2tWWQBh9E-K1Yg__tCx!NgLXt*FFKU=`C-B+zX<}<>{p)t^S5+e4CRkP;~ zHkMbZ8AcTqIVOtm-_6MlTgf}=lT*AVZm%t4-A5bUUQJ+uW;0nm%CEWtdwxtfxl@3K zj#K|&$!%9(YJy_%3yMzG!ZWLLfF$5k{A-El7&0!Kll#&d79~THu|CuD$W^Z9?=del zHjE4wKt_@Ap}k6F$kCbE12|#6!Q8}I*4Sqx8XAaiQxriOyz@rs6b!FhpME%!W)!-E z=nZ0>qx>3|20efhG*r5|QfqW)yik_nR!miJ&i5X!{5>u0aK?qM0_oz!iCH++3yx4` zHI7Z9uu(2e@$yBNA1kj2r1lMKo18^-mB&L2>3S-Pk9nE9rJ&zd4;)m&)hqoCFw{jM-@>q%2blz`Yv$*VFj_sf zLt<1G+Z`02N>r<@!4`#1selY@AA(9MV@Elc8DY0c z7C@2?=@(Yj1jS~p$IB|R4NO$%z6@>|aFYI-d8Of`zLTfYT@Bc;sEPuKx2-1E%uu_Swg&3dy$;a1szKO7<);dQ(@(bR-OTDxs!0mUESRYzJGeVkn z%>DIsO3)So9kfZEsr-ZjtGlGhA=EP$!)hq+pk z5?Bi$uRK1Hh1}==big!USjaPG0=HWvskV8fEup0$1o|?b-4(U1w6T6s!>&H8Q?v@- zB*G(q-4*Kcz12Ny-=MYW2%A4~E-;VsRgV{q5}kYXHJY1)U0mNN16PuNH2QFk>SRKr zs^UGGhCp8OaXb6^Z@_Lb9?wYAI<&|kCM`(gdI6WxZZ%1Qy~^CWKjpN7h$*X!N@@s= z??X}Tr6`*4j4(A7bPatP{}lQ-?fuv&jCYm3C)rZF8suryL9kl-zT>r^&AlD$_?vsy ztR2ObE_|u(lxK#5oZkRNkuXlOPN_V7k#Bnp%5zFLI#@%(CX-lTK8W&htkzn`LPx9v zZv!3%NN<5(jhfe4V&N2aPMILHW^&>)YS=D4U5KtNh`2u&&Rv>=-X)w8wX$csY8lau zF1JQP4YXw&xY)*wvZN{w*SYfIVIeovx{)j!$?X9eDEb@kz*ln)p9GKjg2)RiH?>!bJMekBaVc)WGsi< zSa_MDB~L|#`xC2IuR=KXnorLRHQzEuSSzH1{kwg7yj)&H5OCYyHe9YK)Lz#eJJ0<7 zqrR-Z+_}Ci18n=ep4^_aT4{DQ_(;^rLDx`v%2S`bA+L1pkwERVd1E_5Pk0bz5~#l% z0G*)THB&5d_mV!m)m@D$P)Oo;(G_|@l&3BdHmUi=rtAaq_ZgrF1c}|+!4<6_ySX>@fQp&--A_yPy2kLZ=ln>Y zmDrs>9wLG3El^Mw=Llck=XjLXvX9f*(KUBMrB+4HBL=DImCyE;uVGRAWgnQ;Ze$7^ z#rZbM$UK-+=<9H34miM&;f-e8)cPKE${eIx{MjUz6jRvM}m$xG@G{%ZYoefcuzat7*iZb zCuka6?@pmSF)j3{Hc5AwW}&!8-6tfiNNOs2%KWom9EQFrOMP4W+<142HrAE|^ELS= zd{AEf`8ViGKa~%Av`#3phNLhUJSfJ=`jCe@&{5)z!L0l1y88{Y3z?iUQ&JF4q&Kqf z>G%J;J#p{;sqg+b2iW|NUsTg@Wm8Zo;cE4m!&~SHychbxk~ZpLhp;`lPe``v89^1D zY&tlMS`|(@N}LrpH1hb&oKzc1z+0ju+Xa>W^#i!>vnO+6B>5xyBG5eu_e6d z&FuI+WWO_PJAM?Ww9!0EZOn=;DtPadmwJeM?5Zr~b?C|+QlC%r9r?@GAvspNDFTKE z7BhO1bcS1_Bn8A-koW;u0}st* z9^6FE5hKuKM9O-m&&-JmWrS6RHYU6cEv=n=q$(9kDKOAzC08mU2o6ZFgN7fZ9>0eW zTjJxR$@=e2WWS4ZlxF9jIov;qg=|qPJN>Q&_Z79Ss$z|D6sEW_j7KIw0aLSQ{ zYmvq@qHrSkhy7AL`|Pxp+r4i$%_7aYaZ@Nyglippa17IAhx%+kqV~L4)u4 zE`~DyUn2K;rx8H|g`bCZurCx-FmU#jIQC!}r`ipH#^KVRS%v>@`k4;{g@w7jqQ)}o zNjw@BS3{qUNG9w~Q`+NEN!>dNoj0LB% z!kG@1&SCVyg$R{nY9Nl6hFtvmG*sxije@LARr%V}l?1rgv&UvR?FS^71uh|EHqCr4 z^3i~TEllu_fO?EU%T86xR~_3qu68SM_W5+yhr;Ld6*-~S$h$a84lr^jLJYlpq_90x z9uqmt3yW^FGu@Aat|cXRh-0;RI1D1!&{(X#Ci#0vsZxd2H#WJ(-Um?cjlT}k&{$l8 zY3-bKDNK?g;23&h9D2A9N0eR2_#tJF;la`rF`X>@=b+I6vX^nqQ8^&h`m~Vxcb%MS zxVI;9sRQYWurPfVUapGw*;D%hNo@j|Z+Y)DeKU~!BtN&jqWGK3_k*L1Clhb1W`PXo zlj&~&Mz1RF^Fy4@ZCEWzt(@YzM3a;f4P!Ah1OdG6ns3a^dje3sT;(P@HxDLuin`-) z(JEwMb(Rbo5q}^=EbvUZd8ydfV$L$Xh5r723v6Z9d1K%V@f3}A)RS#!HF@HO&0bu? zM0IRk;seW+^7i2rY@MVee1=Z9gC2G7 z6fW{oAjL1}hs{PP+5>0z_NZewac#w3l#v+z29Spu&-Huh;A~e~vZ*MhoGbH(6h2@) zMejdxAL+s;@%tM6^0yU0&k986F|^snoy2hI!fs8V-o{UP3dAMOFL-H99Q87dKTPsR zbRM+J!Gb^V`3rB&l5b|DZ(}t}h-a2+(Z+4qrA_;)>fmchVw`~RZG>q_L%#h|roPKP_e0|BPrbDl$DHoh2qj22!ycgr0_Nl z*V8KPFj0U>tvWI6yN}>*x`73HxGWkz3(BA+uiZP&V*uucFk1<1Izvtw;^_a!w4QLl zo?TO;;8-vstt?ubLm&}9B%Ws_HnV5_EgnexHiGq#LKh)AH=j{RX6SVlelm83+ABju zA5*MX;?`}d)y!e50UCrZ2Z@|#0aT10;rH>{L?n1RTtNGt__|jGiKwqJNFKc|b`1#F z_}jZU0#29(oH=lB!2N~kOGK+L=%Z7!*KVW&Ve-{iI}ZIe#=FpOZYjA#&Po5SVf=lp z!l;8*JDS z3N$|d8F)qqP1{{ojtA%+-R>uH=8$b3|38xP$3@G{XRVhW{4Bksrody{J+{wF$!*1@ zEmVkp+Sm^J$}sRo$sdZ2%a8K&8e;kJezqvv1gAdNi8gVBL=Xh|Ky|{IxuwQm7*rOa z;dX3b0z_K+jt;D=JIVjTtLdDYS>RzS@y%m0qSHBc^bK*QzpQO&mNgVokZ&Q04{h1P zIi$!gFn?Lz7X$j>Zn?baouoP6W)i@-H$tOd2?l$QJBwT&`zQ}e=12R6eBeeN-p&b% zzR_c8O^1!6je@4BWL941e zJ-4cBxzv6X8X6F@JC|&yQQmyZbqN-rz!~MXGIJ=(Gn>~<_o+{3s|i!5vdFY)G<-qN z6Y69rHO+sHCPpHlsa=Z4=pU6Yi_IrBEuOMATmo#D`SvPpOIb)Dr8nc2bUGmBB-{B-Fy{h*ihnY&}pSD53u4 zo7VG|Y8Xlyz^Ey8k+(L-!sm2HOk78HM{TqO^ff5#ucU0pKQt6rb8#v6=r=XcevhFT zYweOgzeym4iGlK^30r}2=h2>O8FBHtQ*eoT2dbe99jVOHxBLz4+BXEib{y?VmgZ+} zx@~5Ahvx6mLF4JnG%UJxl*hFpcZD@va|NHq=wZ1Y{Zr+=-c#YF1gn{{JSu9<4iQG` zqoH)W$c1nlS-L*-O)mNxkZ=`jg4-c2-AkiA`n(e z68>15a$f`QR~^u)mHb%7=sDYZoST6j5et8H;g9P5vzzE$JcDtWV?`;}b4T-dk3w|hf0eD4QmCH;Nvf?u35k45k5;e25E zE{Welju-73eF^dV#fcQV2H`}h6H5KUfZ}xreu!qsa)EIhNjre zZyZO!Fwm)6Cs$Mp(dvxHxyd(8U5r3|1roIxtN4gsz$f|rD%xli^3tDoeTWo4AF3j) zqbwNh*)s4#lcW!AP2;l|7g38a-(V3)|K>Q*qAO5^MSAhI z#c?AfAL^l0PWo#+&%AlO;0wTX&8bBSlJ>gim9t&5)YEo4R?}j8YKqmtD7T$Jb$^Dh zbU7uXGqynaYC&$O^INheHxmPw7C$xqGik+D`gdZK%=EePO3ScBxgs+V^EaSjNl<+> zkJ*ku$`L*{RC2g1f;QY(aye7L`mA16RtFURNY5wfGc6M%3TXOK_bID=ierwBng(aC zwfcKZ8!BmoPgh?VrXs`c&MIuO>DSa)z?Jv@Go0a%4Od+wvXam;59UV=U6fzmc+(S) zn2#FTD1YYq!I9qkNa63pR(z{WAIC1x3tGOjU+7CZx7|_!h*5QwD6s<`qfq8Q9Kk~l z-kCITMb#*UO7qoo-sldgitMI+SgFe8@w3g(ecj_x{d`K?*d4qvw zrPHy(uxE4&nK)O7Csia3c!?qvabuJUcPspI%-@!7jEBLU^FidND$bUXh)cPOIE@X= zbr3Whn}5|MDsn!ghwTu7PrnN7#580nB)|DJquiU-@I_S#M|3x$PwCC@eJxd;V4{9N ze{teQXZU)W{CiNh=DE1Nuy*Vr~EuZS8>8Ep>%)(@x ze*RppEwukar2 zenhOj>8SF-N@~yqCqsZ;{W=mPgYrpIGF(lrrmmeV`qWoi@qm6ql5ChMO-N;m{?LjPR1+jFs1cBYGI+@XXJ#gK~dZ*iXp0q@QX*ew} zay}~9eartjl|lk7hziI)hHO=uAfNGz7En+g@zvfs!4y|cXmNTpc(`t{@_?>)eWi(o z1$5WwP`TDct~PGF+&O4dkz?@*JzMBnY)oAtY>T8<^`n93upj$we%G12{px5TEwig4 z|0=T@F+AaY4|+_D6vP4@j(O;KvLMI>i0cNUrB_D&Gs62$=wP>tgFwGWiQUSlFPa4E z4b@Ymas|37`w6NV>Kf|mNZ*QRv^|MSzceaDMn%jtc+*gG&AeX?k-nywMag_&2_myC ztIn=oF*)fi=r@(1Ali;YKdr+RY9NlYbonmC$3KOgwvRIRtKjEMd!N+HlpGU+s5z3T z(FucGcxmetMrYj8s-#_eP>-*u(5dD^9g^xp`x!|tX1K5Yal4I=?Gcam5)_h}Sh}w9$%JY6yZ~xvMu?xCfIrPI_*CMP zSk)Ty-2u*rx@%+?C=EEpjy+d)Al-Hg+s@CTUpx*(&SN#sN$ojE3JiVWTDxuUf~MG+ z6~>G%{U=v(Q{aLXIbRMu*bTSeo8M z^4ZfBljZJEh3Q3|d#v{j-mO)&b2b8$1%D#bfRXH29;ivzJ5XY78>$#p+hd)$;Wt);LMQOGd-2oE#VVx$Ig-s z%vU4?s#p_|Bf`tDYcRHj+n=+tRBwEEd6)d@QS&ETIOPc?i8uQAka2`Gq6Xw{t0@s) ze4Ku4f8Z!&vFDYNmsE%IQ;i^2sCt(&u^p-Oqr3c$N`CU(%Y1BK;7@Y6ka_2G{7agb zQ-%#;l($CjgnWgcTmQwI(gya|B@I6^-0HoVKmWx`dOKn{6i0ZIK3MQVo6f}5@ks2dPh_0zNX}#?)#lT&x2#< zsDJ2xWv0pp&cHCL&+H4JD7K+VY1|_&tRjke!tQ@S10idwgmfjaOvyqY=h>tosfVil z)dpTsp7}C3h!ARcPHog#)N2YC7pS`|APLE0lHrs=jg<5+eN1iWDq&Bvp|hCK`<(OJAcwT5{qOROcF(_yk;%aOP1Frf$&Lvb-?`}@s=Jw|wmi;cg+x!x zL>u2LoVb&vmi{}xPfTW((QV0E6&y>}5&Ss%L2g?vrD~#V>t7uGVvE6Wq z+)RaWoQ2trr&-xHXHqpe;FoK>&YOeP*xU{&*1vwkFKCGs(xOPnd)PWE8dB@TSoWz5 z^pJgj9mlicO&v2oLuB|kD|Ytr&gYN+z3lMTxuO2T?Z7aVO5Oc#QLFCFeXHUx4UkMK zM-H3}JkBth#(;dvU5Qu1gUIBx@WWE&x5zQ3p9qP~@3$Y2$Myf=WJDI?>d;Y)SzOa| z5Z7SaUQdT1^hOKSNHO3U(LCG(}t?h)ru^9n)F)V=SL4y|-?(Ok6RT*pC#Rei@ut^nMcifq~oo^KQnpIkn#f=i^xZB}*nAcVcA_ z5AkYeb0 zuyw^VPo3VlXHik3>G(s*KwEl^OBafCLA`As;pCvmD9m@3ZNHJZcvC)pQHOK2c72#& zjJvXP^%8a>N+0gEdEHwFb&;cZ(c#52fbkR(9@wL*M!foOEe)B)rRYLoTuHBBr-?nATX4_Ur){_UM+S(ew^aUp9-Qb^hXLnciojNd!iWJU!4<*Z zqkR@iVD1|~S7Yz6&}kC(y5%-(`VGQ{FHB3gWb)-qA)xGRszdK|x9&@r7)}o}>vXU8 z^A^JTq5kQiFmrH0028>9TFC2Jp3=Msj&s;&VIrb!O>$3{R~FOT+7z^k7JS&MMm4yA zjNzyH2}eIr^A!)yLO8C)$0K<_BBcy$zgX*!LCw-rD~$N=P)`|U(|E_V)RU+eXL@lo z@`(*jjf6~B08qkV6==E=jXnvhuZ8aVCI8p>Fq2Q>rN(P^^M)l;%25-@bqvQwxTeE!u6QwiI3+`f8QGnv3rK9@ll#A0h{{2Pu%0Fv zxWcqFw0U>{xGASM>2=$jWuIm6?$PkQ#Xj5v!hT(T)}BHfgdPRQH^toH>MY6d9L8Nx;&sN=wH2B(woy}9PaU&bo#7u&yiML6j!*~{TQCe z>s6pilvDhbs*hvu;d}z4fj*DnLGQO{HP$cXBZT}o&E520y02BDh|2+Y=f~?2kDucn z&F6PeZQOkv9=a8f^4h=*H8~D@FeH4d>@jQd>+6dV>FBI)p07kdZ(D%rzRWJfNB;;K zqTTvbg04DL_w!5Zw`EQE_K?}v&BD`LRdn5Y_2aWq zfoJ`C$I`4E(^QW9mj_fr#idW2G|53dbi2WU!^PmDaGj-x7&rqj^CTX0Nc`zk2m z^N#JIZYcQpIrC;Jibn70!)J6y2mxM;j-exuM{A&fyfRwH48B&;dK_!C(#uA`q6^NC zhbO0x1N=`pqw2I2rewDwO9z`n2P@dzoja?ZO=5B=k+qrbEra6kaI&*wt4Z#dE$iQr z-mCKx(NR8MN2LrpA(i%{L|#hpgAgakeW{|BGsX}V9q}Rc2X*9~=CHT==rQkrhr5}~AoCT% zChxV-7Mm{(4|RES<0!Yn=z4Yp9?x*zb9K&aRfj#zU%w5_FL5^SRei%a*iGA{n>I!o zCC4rfE77#GreV08x~;ozON5w7ggiz-a8#kVCacNskMFc8o*QJGptSl?D@9kM-+wI* zYRfaQ)}-KaT+(P%GYPZ~d;7F!*PTfx>Fe7E;oYdK5Cd}*#D?{%5mlFgU*f$XsbdRV zm{6JvK}!bN^JueXck-0ESB4=1_Oxn?8p8KqF+JkVVnVPw97t{>^$YA=O(i*boo!9! z--PF}br$3l#W2q<$pL^}pnsq{i(02j_nBvFaCmTkk9R9C*Q-0uErADa&rk3{7{GdD zZq=E4+17FGBfeaH+bk%E1?#kDmsL(YS(<0ol_6e@N$S1|KGNd9fBHDfB@Hcn|G+r( zz=3JAfP`s9G(Tu5X3Pa^W1bH^{@jC#<`u3l z?do3_7fC!fmWdnO>md=TOdBK?Qa@~PL*dN}O>C5UT6(E$^%jFrretcb_dA)=HJ>Ax zfT9GMB8i{bm+t8fQ}w0}CV`g$1!-?BiYig2c17Tb3;I>$^jGA@e-D9m)97nxRP%Kb z4Z^U7_|W3*&7)}wxS&cxx_-+OOc4|1<`NNL7oG^XwicM2H6#i}lX&rtUjl;)56B&X z&7yTRm+rX~`?reU4vT(!FS_$|$8YjtCH|=eI<_gZLd%)q=C5D!+G*r_Sdl*H_%q`2aJi zC-kE|r&@C$(b*9nWL1SPZV%id7 z#Mw5)R_#ehsJ`rn;0!`!GcZVl9$;HGuw8WjEEo6Lx(iPP$g1lb?OEt%|HM6(*z|LT z;HV$-#=NMcNwx##FfnP|)uAE6#qFxJ7cFzC7F$%ay*`+S7)y%ivJqnrSvFA^pUb@lm>oCZ)A@FY9VzZKe#_gj`Fs8^mzyX^P z*ch4=R%g-K&tA~*yLu{L03a?s8&+TfzjJ`FlPZ@KcP`kfKJrP(E&7x?`FfQLlRPT9 zAmS{43sPJU*_C;ow6B=0GF3+eAOsG@<4}>u&Y!8tSDIA0nk6gmwS+rDX8LBwbl-3# zb-kFkX|~dy!Rgk#>DKW(Ksd}(oEtRd{|hyqVbyjut77511KtaxHHU%yf~<|93)WNb z5$~NDDuas6U(F1&8|lfaXr)g=_>}PT=`x`9hHa9{-_O%Dq3)G452yRQN55iFbx($2 z$rqTfi_CE?K@b5l@HtnqjL;)eh&0D9SZ{kyV)8if-(&g`zyE;yP+q>HQGae8h4E?r z1C!la(p?>;!g1%C+s3{Lq#s?5fmIzm+JhQ$EPl*!Y)?&c_Vj)E?=IV9<$rEsd8D#q z@Zc-!jDahF^nc&4tTgib94Cv8BPe}tK{Bv6ZF+0$wupGwNun%Ko-tfZc2%F>8w=@W z>KZVUKSbCzo|(dg4pH>2O6nc2VPZJ2OI7N}-`9^5NPlG?ISi`pG{tK!R_o_0SgeL; zx-a>w?FcH_YCm`m}a)aYwa<6K+L{`JI!eUMP;KxL{~>1`_10k+CrUUPsoeOWRTek zBKG>}su$Y`SJ6Eq=77j^LPpm+Ey7%(d?Kew6OdAMmtlEs>JgX7gGL3m{&c5Hv-{}` zt)Lp({)g$AI3`NTPKjDNH71OxoPoKUK@5VD(%!|c97S?O>;}hnE9rFx9|HZ6_P|#N zR}C_kao*ujofeX~U3gm=tLV;^F0)C2&F~+ip>MKfR(7485A$^cp7A`hrBT#WTz-yY zV_H87N+ajN4Ve2#&9$u5bOh1_K$1FpG@&OI{41unrf-|q3+(V`2e(%e-^;d7aoq5die3B)mlz@kJC~(2>Jq>?Sc1v>jW9h+o7S~uL5+d`MBMl2F=+2R zkdxQFIhGzwqESq{<7DLrOX5vTvf=7}Q{?EbCZxHTVR-?TSpqUL&unxhspDD`nFBOVhyld~_YAs!No9o+Y!^x-mrvYuosVhDSb{l??9)R)Sz|HW!MFfet)t@aC z2;JY@pw@(X;75uF`k0TxUx#*oXK>PDe4rplmFs4)R-8+z$ZAgNjp>m;%Hq%(@E&VM zNwqZxYVM<7i=2V@#69!$=2|skVCs{Z6#E4NfhN=M7TxAD=3NWd@0?0f#Q^WxdTx)~ zZ2ckTLT*xLkQ~rQ#m*f>mWx}gdatMm*+1W9ZHX zF0tn=Z9LIi+0hP^Xk*MDNXo;Cfzg5NrHr@sOrwVgJD@senJ(qJG7{DWNrV zFiG4N+A!cQ|943mw*Iu&r>=^>EvWT`-hPUWGF22Dd=<;SHS*cWUhNM_g;PTy2 zDM&T>{`fQYfoM0?iRhj|U1;@qpK&xr%@-clDt_pvozc5TmC1#tx=!jy9fl9L)8LwJ zHwvsQcEYw-o1cj#m9(~G)cS1cnI6kz=A^^*5!8vKWCiib_MYcbjdn#^+nu@B^NW6r-gBN5T05O$y)>?3A{%{>M*s7r;i>4Td{BExzf((MBe3Y zEw-2-g534u>7UK<-xIRXu&yYcaNf1(c|3{IG1A_u1U_xyR5?T%`Lm;qD)AGs(8B(d z+A8b?!53%Vk*&Nh6&nQs_d4%}hAEyFrhAqH#0OYys8uXQ(; zXMA_UU`PYzxF5#0vUP@@^$&PX)I@<`72r%qI>VySV{});Jz$1RlTWXOK&fWS!nYui zN27)mR<$4h7m;@(Bo77)s>_e3!e#*Kfn+~bzdmS=`#<^KU~~iLa;x%$CtqE#0o>2& zk;q5ly*8_Smw8i9(a3e*usKS@Li?j7euNA=eLxYN6M6puOlSy_h=GX3%5?Ri#lfi> z*o@Y#khOePB}jJ-l~VyS=Bjpo)T5qx-UWU$=lmCs5*s)*i@ zqfCs{CEV_oD=F4*LqmT_PVkZyh|ky^a%q~|bt~{!L4`6EzNdy0q|G;h=yZM-MXa zgUx=XQ}>q%-#@rhE?`gJyIx}CPF}UFIUt}-=}lQtjDyUUKmKg`W>6j?f(3q!A+u$N zb{Ga0E(tZR)xpVF)Lkb8d3~({iARe0fQu->nL+o=A4?|ALk-yXh;m}!#dx3a;^N|* zjqt1-#0Uw9Tm1{AsgAtPl5Ivm0V)>1CKuVtG1{J!KC7d=Fl-fi8hx4W-ixwTDUN2z% zE#KVI#uf!n1P%*o;SLm-17{%siHqQu;nBut0DQ=O|Hl-VA%TG;jYzOM?O~U^#Av4U zB#U8Qp7Xau4e5t?#DpfAe;S?{7|=c0_>zMQ2e?__?84_ysAh-})ox_$f3Vd6gHiGP z{7V}bSf|gyYy3O5dk&g@W=)%VP|TW92( zQHHMgXrbf`%nx)KGF2*Qg>78Xr;A&N@%nK>=7hjgg)5 zDkF7;qLMF8bF2zoQf(s=9=`gT{)iWo*Uc%}Nx&PkLa46xa0LGjUZla(oY1#h|FjS; zZNH5yg+E5bfRtKk5%Kxp+oDs|RH6F;a}*~oWQ=6S*#%V8fszxpKSXZc`Y0%K|N6mn z2bfk>eb}rTT7}n))w%CItkF9GRU1@Z2Bg=2*(6UXec15z$dY)cPRx0nrG&&%*n#{D zm%kL)FISWW4)2p}yYoy# zi+FYurt9~IBVHMtvxU3AMMyxdBo8LkB=!WLx@-(Rl5JvaE@_}Swvqr*NoY>;0ySE4 zlGbhZK=_=?KYNMHF94LhIB{L#F2Ym9{3x&~!g~B95fOm%O_d7pX-wqoKC_5=LLD$H zSsb*@W{#>BXB)R4lr17+SuA~gIg)n(bge8pE(U9WZ7g$M2~gLVz`uB^5XVWidt7D! zUMH@KbH0lt;nN$n<)Bx-G;l{XIk+a&jf$|`U7%jc30rO!a+wnrQI|%PG8frK|L_c2 zVLfN9)&+ay_vhJvq4KA;W#2{tn6Hmt03m+qs829qwPl4c#f0ZG@YI;n@bjE3A;%;K zwdpG5>sQr>Tt$f;q7XMRSx{2J15F^|%&#DYBG#=w-`FGQo_#%H3DJr?z(%&y+GgFP zaz%ZA9`q;UnT~BybI9pDruaa395)q>ynQ`~6Vh{fh5#OyG;!VPjylyI$FTY}Xbk|RD%_FtBMLg)DI0jlfu=w`OD3 zifoSQF|&LFzkXx+6yNWU6lFg!w0lai^-Ls36PmM?kqV@nW2zA`s5F)$*QzwIIQ~$~ zlesT;TrZ_KieQh~f^od+qZ_ss0b8OwPXa+=Mj!&eo02o$ScH$syX)J|rDF13@+=X& zy}>5|MK-y}ZzisfipsX>C1}(`JP3s45Sn)A@60m@UH(&_T}weEHXh0Fa<&Y?+Z0?0 zQP8M4J9_N%=@av|>n7zAD4}J^3@klDW4Q-*CO|?&>oD}&*&?~=3(MCudSz*KPa;p_ z-%IC9X^AhWTT!MIi;D?av=WTDzXLPGVIf=(`u_rts`+izD8(y3`!7lX04_CV1u#9% z`ZhwTMq(io$v(GhOj>P^GiI`%BQhnL<~Q;g9@SXgW)=N z@iVpLOU$c)>%Uibbsx5|7Te3E~*(iw3y<#|eia{@_ZApNU zZ=&4~5u*2^CRj2}tg`7b!3K*&+ow*x@2-gHxMH~}tKHd;$%27(H#7?uEY0Eknc@$Z z_BtkKd&B;e9kU}D33l8n&?5DOZyr~Ysae9vE*2w!$l`(F$G@lV&W5^9vV?%~Ev4pa z{PqGb(bZzQHQFC#1~FMk`Xjj-2%fKt+H?)o?&O2!UCYCQucOQ}aH)^*?q;L{O%;*H zi|cUfv{9hgExCGN=)C2-Q~Qbzz|QHwk?X;!a6KqO-(;>~$lW1X$LR|!ANOA2mrA|c zYu^Ws3|Sa#nZv3kw4GXIec32GoN@P1^Yg?ScK~N6V9X5B8)YU&N_7&e&Q)apOu!eH zc(TK-e2ER-9~j1th|Ts3t|(fpEB?pewo;#RrwFMIq!GVH4#8NIfKZy%}0N|YD9D$sHp!j>j3T}zfp#m;p%N3QTB zN&djZWNCf~MM%C1gZxNO5dVEb(MNg3s=fwf_bo^r!@?IQk3wPh6NyBt6iDb?!d*Ky zXv*Bs<)Nr2S#Nm9UaX-ERi+H^%L+`ug_RXQSXx2!Ei{bIlgdR8BC>85D^}>G4EKbH z+;~DED3Ko9Zlj_Uj8j6KPxoYF@JZs<)c?y)&s)^H@@FacmU;jiT?_G&f!n3 zBB@*;TwoQ6i{qkie!avJo7oY@fi63J(C{r;>(lHuJ;=sFS;mc2WOofo%iqgd4IQ#IK6 zu=R^uy5DD3QmvVD&>BGv-ypS#Jt3t%@@rEY*hyn_p>y0RBdU|YC(QRhq|5?JMXFBf zbS`f{fphOk7{@Wwh|1~)L);0rD^ChBT1j-4y$&iAPFQMgSDaTImZu}^WQbqa1iY~f zga#qCfPxZc-g0pVt0ys4XjMjA6gMf{L3Pb}d1@%+b^@wHuHRI(+)u%HQ~O%d?Q&I| z44o|TD^y8#B5tCt7JvrA_3e2gW<)+NY8w05gGdP;a1dQ^kpA;89?16-Zu&1CP-Gk> zYPPfg;*n7`L0z>6nziXJtFE;tuWWmn&{H6yeox#`qj%pL9P&?C2Mc<9Tc!JfpGwAx9D^6d- zg4Ig~3H;QKvdAr~qQ7`QtN!A>M2OLVLxKF03oQOzV|{5~*(Y_R9;v76UuT7a|CSiZ^{OxWHF|Qip zI-*`fT?L`9r4E2bl@i+t4CPx#D}9G<0&;JCehyP;qy^`gD|!><=0uefP{*gIO12jv&+*(0z95JwI$z$KvgbLMd`qyRJSzKDfO*#gJOw^Ni9R{4b!!na+l z?xU^=^vh6;pTqm>4>iS6(~Z-v_-5heLdP50-qANXQA$`;CG;#GGa~o?|m?ujcQ`+6%?e_ z)RuRqmWAvG+##SGM+Z2@&nnp-dfkMZg--}i+JN5|-4-!=llm7LZqzIrxBUTe?+0FAbdFXoTGen(%~=v*XxcN;~5PRNl=ZSj*6!{MxphSx%N7%gu~aRTp+R(Kv5Q0{ZM( z^i{hQjcC6sgjBOomS{(U&GBm&(ne(^3lQQuL^MDAi)YFlGeWsVVjOw&gGSxYz3w#1 zgohJ$vWpVE4C!7hWp?P30=mZmDeZmCt7u%5V!y=sU%W%Y8pTu$mF4Vqkz|+lVN73D zM|#uDMg8t(Th>r=OJxE5q9_w3;3=Z*AB;4Hm(w1nI+$=F8gNKHg9uN-M`NT=lo64j zoREmW3fCeb^-AeXC995(j$VG>zfTXL3I3yF9g8$rU-4JQA<3U&mO+n1KWvPuK(?Kd zcdZYWJnQ-Td>ATbtRfVtA{xf~1c0z!L_ORi^b+{#PV-^S;zo0ZDX}y;-jQVzc9Uk| zs&2x`%PH&nqa3q4*&+27Hx>RiYLWvdq|@7q@XlX9rjfof?;U(@6Zn~}K3^!N8Iy5i zs&VrPvkR+DrCDCm`yHFt$KdDy%GKREruIzgbhB_Cl?kBbhYQp!yEaPg+N{G)AQ6d9tcaTe6IXIo`U4N13Mb|i zbbV9@jUY-T!`kvXan#POz)r|I$?Bcg_$#m74c6;Pjp=e^b?^?)Ae^LQIO!U~3QHA5 ze7WRkJbqQ1Xr<~MB%D~stej$aicdu@!$cI#y zw##4@WapcBO01tmMQ{1(LW=Um+rIH~->^l5aEu zosW^bY?0k$?cgBj`*iYcK{TF+#iauL=rc6T$P zIUc~e8S7&LB*0m?RhJCv(k4fDNkE`VB9Vzk&UPpw`Y&Fo6M54ZCKQ)1W*mEEsLp8R>diTEH$=aA6Wu ztTcXttwOwT6`AjoT8nIq0j{(PB7*`OTQ#BIx#co^BM(wAv`dDVC=be><$V}K-6@f2 zn0}2DgB>L(_vX_N55+agWfF@RKhlnWlM}>E6>uF&x76D%CM@tR`(>V4Mq(kOBVSM_cWwcLq-20+Q6mF+1Z`>>i}4$fgcIfnDv zCy~va56X;NL5X>lFDa7NNhcE;xJ$ydX#h{BKK~cKOn&&!MEjta?dFQ9N8~2>8>%M| zak3X%xwc3c<0uxpd1L$HGaXl4G{Z`O87AH&qkolgiFPuM=1r{;pLNitH;rZa*Aar< z0sK8XOj+N%Zgv0~VfSQ?cMr!wH2oo(^LLZat|F#ZvgF8ijcOT$!G+x#uvL2AMO5{2 z$2*a7Rn(c;FQA}lROFK_D+Lm!{04&<_5Z}sjoBhwzwKBAZBnBJfa~}kr&Gt0 zkv1~$!(dBN|97e?P-&WnkonXrrO;LTe4BKv^CVg5*)^Q%`z`@>H+#KFxF!?rJ8c@2 z;q&|QfU&gHgfTsjO9EkWX&EOU48pKxY3h|@3X$4Tg5NiqS_2g$LxLVo1b5G*NgTHib`gl?z=t^}WsjtGj?zgJ*NeMawx`9SHR`J#f&5&f7UdrS|m# zVO8pqWZ3t!XdK6J{GpYU>$tfp~*`Uy6pz z`LQE{v?^v&#P$@@TwS$h2})Sj5Uu&OkFePTE?Q8}yZcQv&wcWKt-9PQFb~+OKSV#+g%BRH-q)LJQ9261CoxXza)%WNj#ivqtzz0w-2ZZux;r~b03KiGNBq`P@y0-W#PAG#+zYz#^Gz<--d4|Q@eIg6pb`uXiqh>#jf35F5U!# zJ_&Z9g&(p=H9DlzA392u7h)~rI@3I$(tjivJ%sd=RLoCGeGxT!%Y@9mSk0CPrIJ=j zBoq8`dxu=#6CLf<7DtqHAmk}gocX48Yu5-pC$%k(Q=WPdTR8cSFeoM$QaPT2n$=Mo z;N4EHYhe+r@=Vmq3mgha}&=`C-Yox97L_=7C zj&u9r&(~aK^@phsf~)vyT@pSugel4kx|-)Cq``3w5-B1TeWRP0^&i6>NVzOmeE<4< zt_z;mSGHg`YSqSow0khG#1Sc7D0d5Kzjmhu;js7}1W}!MFQn(N0EqI_Hc>5dVek2I zhSuj^WdO1*`lCJK$yhucbIgo{z$@Kbd77GJudWcBvBZY*W=huUE2ZGIS9-EfUaW^f z>IT#N8&}-UW73X4%|bi0b!$v3w1$JX`xZ$6)Yh(U}b zZP*{3YAA7~TE-XxWGDrVX_A1z5ywv>NCnhnLO>fWMG0N(r~w1FG`I(I8b=>J0Fb~U z>m`RP|Fmz^kC-8xZ>(|RB9Hl{?{(!Otzz;jek|{@2x{O?>TQEfiy8{`5=Q%kJ$Ndw zL}RXbYWBHkKPC}s;v&ue?Np&G68#AJ?NhI^-?2ppcY17PInjc`u1Vle^PpmmkIZkc zYO^JNli6?rQ&w2xx)Z3h##ad-$&D*|o^FDQ?Q|6AYf9AUfvaJz+mrOE(T$d{IqOM4 zgk%Jmn7{UT>DIOHqnw@nbM_5pEgb{dVnMAc^>?i9n@NBq5~5?5Q*`t1xY4 zL3&Y2W9;a_Df1r0`a0Ito@3gEub3@x_)cv>bzJ!+;eC(nXoC5c8V_efzdd%Qg zik2cXPwcR}bX@AV3GK7YYcbAbq=Cx1x+xlHz=|VE&-I+H6hszqz(C1gc4@mEskjWu zlrG-b;bQbU`=ZC1spF$u`im;$OxIUDJ_YxVdU1|e=b(;r(cNIDF~nfi=B|a`f`Az? zVfF9vo*zq9p46CETK!!&i>n^glc|qbC##N-v|>nkvZThUGA|w_(E~60A)YpGP9BS? znh$XR$tb=aDEkQ(IrRTR_Nj=lbi zOc5;15nnYP9WgY$=+z!iT4$SkbS5mDGv_3%KjEp%4`@o%&{^z%-rZ~%X_zoEus5(! z%np1@$UcT@vdyevunx$mu_QBpVKp1SXi_Uisy$k?Qy{eeC0O3@l^|OqoR8?WQ`W$B z0z5ZLm?3@T4>3oOks{UdXFy{NRRu68AIGrcZjbkK%~ zVoT2}r9a0ODFa|$2&;$I(%x%dV!pnA>pc%_vk}IgXPYnT3WTD$rk0)`lLDjcs>~2^ zCkueJe6X6_gLD1VTua_Te2tLu2}Ot7f-t7JoO5Li4V?DMU#p&w@0rWfrL?hM#cdj03H!Y*F5-E zFyW;%sqDQm-FrY2u`qd48o_H1LVDB2;T^QR53@KBRXNCUz@n(c8XShyZ%6Wdz_!7L zj{^#9EwglN?!O$Xc5v@(dX!H}Bay6nh$yRi2&`B$O^S~0d6{w=JLZCer{qm z=wtE&N9>4{WL^Rz@7!h*a<^FVZsAGQ9`cN zjPnk8X)e?v)UH>2V~q5)xXaS`q3%^oQ*zw;o!x~hZm|@5+>>(L$3Xq_py4kg@g>wA zkbZseX&a*AXk0Dh!iaY+f}B!xz36LAX*)XwvFYxc1K+1YwnQp6xv?X4FJ^mct<>CY;T=9 zXdS1Ub6J|o?%whk6XmmjipFO2NU!9n3qt}Ib5Ep@0^XZSOE$On=Of~;(B)W*vK;>Y6_SZT_DUr4@NsB}r-85Tu;#7T6*ckVQLBkOs(T`SXo>^j$I5SPVYyajk9K(%rp0dR!amwr*!TusRgzHf1xsHx;; zch31BD7i$}kaymRo=gMNFVzq|i5YaX2jl<|vywbtn*P}OCleWTvq;xK?R=;rmPXIB zQF*~%(@ z*_-)!n|36Zv}!)ppx}>Rjra41?E)DTm~8{`yzX_shEV^AhJ>wC=?4FFeW{Yac)b|p znyX&1GuclS(7hS3zF+Ge15dFKXNds49e0k+0=^B}58!ArC7HJgvs%x1^aY`+*Y6=? zaMc2K9Lesob5VQq4kQjrqnpJ{?&^yJW~e;@D>*U?(%x=N9wr^VP2xh&!Fc&1kl58hLjQFk6Al zA4p9@C*Pp`aNBkIx_i+Yw>!~yhB!t>>d{pR2Faqle0p)P`ZFEntqkZN?@Wa$2vw!U zq|KwH@)a``+jQ#v1FpB5X||s5Xk#Cb2g5m-%ny=`0g&twpc@kc@O1E4qn)ScguzAX z4o;zqpYMG#R|wOz$TA$E?}RM3>1J{<5$oH7Jr@F`G-l~g6p zyLPT4mFxXS7aX;Ox^r+>d2wpXB*a z8fM_e85~O|p@EU#Zo}TLx$e6@O5%rOdq0r8X-b@kBj65?EIWa8<(DQ zUle>AgQ%$5!K%F0d*KYt3zfUffn(I(eV>lBQtNQ6xbYsGyZ2)2a(+xx71W_JbIl54 zMh^k*)#FkR{1r~(lO!2X{4blLB&G(VU6KsYl~RH#d|LGd9sSjq!u@*66bT|nGOI|~ zGful7^c@rSKeCQMIglrmTaXomc&?xK=B*Jz_70id=Rb3Ve5YKB!8LyXg_LO9?1Th| zAPDEZ;FJs^#Wmtx{Lq%IJ~3$DK7L7&$%E~Wq6b{t9Q|QJ#@5tPyM(Wq?@Uo%Nt(iY z+in}PM<90y8Jty26BSmN7?vDM(t7zL4>0ToKRJ?~6x6TKcn-UyRY~SC|HO`Whbf|} z(-=CgMQ2yksd;8fR4mfIKvm|OkSOMj*5?{3R$q8<>Am%Xb^FA zLyveuc;CcIvilg>D7{qix`I|vm8=xyUhaF(re+rEsr}o}m8Qcc2gcjOLX2L7-wYme z(#PFoe zvdoYwcn>D67Y+E%b8m@pL`te8=5mo5n9Mj`^}l#4h*xwy>KW@i;dowWVuT+=y<#D2 zE-j=R58U!dEPIz~)`S*+ahO7;<-Ywz)T^yQ=Kv6Sh4#Q{!_F$TWQ{a zIUo!)|FdX=na%<}dJ=Kt8rAWH+f$}bxX74@Z)BRnmRgV!!B3DZ6_|i{zUC5g!vdAP zFj&VF8~dlj#SR?V|9s))ETtqD$P)J7x%%j!NrMHlQi^zmr|wAa3*nUFIXq!Z zF#Cb{sAiYG#wTN|SiKxO0IM7zS>(TT74%*%CFA|w8T|V>8?ed^#mTpl#QL`{dOV)k zHQsN)xE=K1M-%@!oFIIEyKmRV)8Lsk0^2yEu={Ytt{FdOyyF?IG%#986K zySBB=8`fu3J|mzMiqVKUd&a(^%=hO1=ni~M)RePQke5t=-?h+;HIE(y?X#CAs3Q%u zd+s)+yf|9<#~lLB{5{cDHdf5HOom+VYqj!YhCZ{8(S+5xcN_cd@r!UBUDvmJmQ0qi z$w}`O*HTpe!!M?fe7wi&Kesyb+-ykKn{;X>0NVjfn23zYqB1hF1s7eOt22QxQ6zni zuBn4xwdmQY0KdzV@U^K23tgbz5?4Ao{gDW40wm|{lTJnJ&(%X5#V6$nBX5Z^ZOSSm zc8X0(k6_~s{G+OGpYk>;x2tROE@9K+k1G|i-CG{I^v^^I{#X{yo6U}KZkr@bJ8T7| zHNMK&1Z2G~C?y)K>^+C}BYblTQxXXR_V=K7$S-7(|3um#oiE9Lv4}_3kb}6TtXz zsxR)ShLzgvORX^krA@MZj-P6Xsc~S48&|u2MMz;X;ifd0^{)a%Op2qv0dzjGHi=n9 z)}2lpj&CXUTO7EQ9U6jsUDqbJwduj4LGmw@NnEZJAzPB|)TlfnzJZE;Ch|7;euMD4 z)43~-yc#2Vy~h`GZo>j`XvF=+@?rhSzb_BJ zkIODLX4-Wvt}*mFfc-b_BK`7}Ncr*sP!iYoj1Z%%apl5{+P#H)cMeRC^ggB%eEr=3 zO4!(^5LXdNy`E^T?3yNJoaBF57a0(;h`mHV$l71a!ATJ{;Etqn3WHX1=Jk0w&{#fY zU9V^-<^kUEMPyq6YP2bEo{UvUA;dvTp_c-xAOSCL*ya2#@Lg2-W#bwMZPv3ezhw6k z5Eg*gF)*Y#f=H8W!sJjG)og;h4@igCxOCQ|7rSmK+kUev_MB)9q}_B1^0^N`78H-K z{9aNHP;RdKVm7+nAkyq8b>=thBH*le|<>X^PjQlf|UY6b~veK`WWobrE&1^}S#tyg(oB%+f$=_#Rh zEhB4turxMaj9m4>XR&3zZ5n$U1)&#D4>GnRofu2GVq1s8E|`xbKyxqOE%mlC2R0CD z3{J~UE;ldCB^^9PUL#b5T*l{ZV)ChW|1+q;6n03KC6&d>lSaEY(qD+MOC|c9HgpIn4l-crRqMSr_0J_HIQJ*L5 z)bMBw?z}LbXF(6{30;7VeKm8utCgEjYC&727;$U*n$=-=hGzsHe?x((FEmeLa3GU} zU(ysYL2t?nnZi8e>!B`drKK#c1l3q;nP&B{)&FZBTu-PqJG0|HC_oNgH-Z8n_#1lc z;PejFj(r-v#?uXe#180g=-#n+I}2K2oFuvI(WeGjyAb2bZk?YP`bV3zPJ=CjZ{-4D z4TLhBUaU*UI#IK01%|PYSF=IdMh?X2rh=sI$E?uDxIS_6-X%2zr2)oFhgwtK^MNC6 ze0K%5VlXomemoR4z(g+ble274@Xx5=CAe+#xE&#&xa)AgQH`Fk0Fde^5mXFHF>*7x zpb;9%kkAb$);@)>cm2xJz!t5aIew7O3EX{JzfN^p^^S++uGe{y)wAfuA#fI;(90=S z_KRv$%?ALFzHtbh70#4W5NQ1$n%)8`itqm)1_UXo4=vr@9n#&fyM%y9Nq0+1NyySF zAf3B}ba%HP9ZM{TbT@*}`2GE#<;?DK&TtNM=iWQ-ctxJAll|dXh3Ub>;GdL<=Ui0W{w`)j}@ zGjMR>#d&MvesoBc9)u^%TlU!QE0?|e0I6$4+foA$`XJEbtkt&=N0iNsbH;cYh{=RY z5~v^1^fB{Du-iCgIhlFbU4j7BaLcyPk8sA7u=9witW1HLkyCL-0yz&HnoI}fefq5P zZM%WhrOtBoP?}z%LFuxH;Qob;uA(tALvte7l=ye6Afq3i1Lw%mop zr#j6^C3Fz~PF`-#Ny64D4bU!qJLmJs^@EJnr8_+mkjcq|hsZ0&W$H7vGCy`|2(inyhKr;2vV-1i`-eTeowTq!Tk0=&$ za~4PwtQ{_Ak)=McO;t8&NW0t>wicEN4C55i6VtVB#>U&v+{D&O?K>byKJQX2HG-!&qRKszm;9~sZY zG25P8>aPr?mJ;Kt@Do{3KOB>=GrCstC`JGrlTN3;xb~Q|+ zw8*P&3eU{j$j0S(Tf8mSVD>+5L8@|ZC@?&Or+NcP$^!*`pn_!xLm}MZN_z5=o@}z@ zfSEd7F`fypvny$*oNe$=Jtf{0?z^Vl%agC4j#A(Or)RVGCk*e{!pe(YL<%!{tr~n{ z__0l#@MDcc)qU5vB`4xIZx5`8K}mLbIin4o?a@?^GSm3U_B%gZqu`Y=Y zozo{S^(S%N_hpFxq}6}uZk7Ji9I4!7#6_u{j4eI$j*HqZd>_fR3yAv7#{6t~=~q>J z01suElTF#r9r5AYZvA+!ka4gJRY=D1@2!`o%oJym&`GBxV`{U}VNunZ_rH+EdUAXn zCeoDj5`($Qw{sY+)?QEfZP*K!$+)V*GE0xMW`|)m@#fVNg@iAnU@(%sDu8o2A~J_} z=2~A0&i*0=r7`kDR88UwtKZ|L%`N@My-Q~|H9Yx%;axY#kHINmC**YZR}cHI0Wcu= zBlbd+Gr8B;8DJoui?wloRH~?VaaAa=K4ErP12+ zl>9=`{^Nsy-0iBx6~r+#Z+D~ii2P(Vii$-Y0aI>rKq{SNlPaPhtqIJW?v{Q zr8Pqt%UNBi>3=!3O+&QjrL(6@c@o0O$B&<}7;#MPdr1w(ry&_i3?z?NL3PBv|0vZb zFF2lR$+|u(17MW0-Upt1@<6gvxCwvfDo)=e-0MvEUFoNWZP)O;ui_LV3%vmnD#xAp zrzb*e7^crL(?HSJVxP0%0Z+*kULi{nPx2xY$zQ0!r{AWr;u#aI*7~TcEJqae#Hj+@ zIZl;MW@R5VGBedCElopxVk(sXsi}|l8!}dX@4{nIRQqk`+>%X9MQycVB}-g?hry%Q z*#t;KMGc7r-yWf4u9SVvkA2mRXhlWJj=11VHq28B+lx(50=)!jln4`avF-8_dP^j* z+bA8EZ3t>iNmS-DQAINpSsh>L`mzzX7sM0K&tyrtq9wLG%kJNnIkO*j_qL1qGcy21 ziW!$*&F=N~Lx=DtC(3!~*M}=BHBX@I2{SZSu>Fb1zR#4h$)*Y!RklN1QA}8`@Uc!f zO1%v*(K6Wm4*+GKS+@&a`{d^EOd5S9M(FMdQ<+!L5DUqX#51=!UrTje;x4_rojD%v zPqRU{j6Y@NqyH-?^dO(|;z(9PJm(WiGME#~VY=gB^-90lswYh%*B|nsc9{I}EXbmp zC*K%le{0I=cPDnfGhFk9GGubvtEZGtjKhZ4gUwsSR3tnTQ)e@m0Aj>26D#I>RwzQH zq_=to>LQ-pOlS*(=uQ{P$(&h*kfW6w^U?~tuO5zc^bHvHMco|YPpcN>m=T;ymYK1J z4IU}LVrmt{yhSMJa$F>cwqZ0C=<8h77r6G*pR+0nz z88@}@;@F|_fg-a$d~VcfkQW>7PnMDt--KMcOj;6qWTN`b8{Hql*6xPaQ`lNjld{vI zi3FdbpKPBGPQ1j-s*?>*fi>MG3KnG_&z3*%W1*u#rOFC)e5p{8k{ITOj1Jb?t3f(S z>+gvbmM%N2kG&xT0xA^iL^ER(hLhO6>wuxu6o|3})dkxa>yy9LpVe=g2|w(woy(8| z^rjl_Qu#CkLeA6|-E34@k6X$P4y$WC<4n!rT6`Ef=+$UMlqbF}diV~~f->>W`u_<; zZnk3R>%8fn3Ot<1oyNq85eQ*`%zRyoK|+Hu1L_W1znCmlZ!07m+19nYp0<5SHW=^` z_MISdubfhSw>IEI{lhg%w6e5+_3l&L;F5R)@Qh3T1KLAMFCBdS1KPFd6PRczBo3#4 z5O>gyn&hbGrR5Tv6UF+7b~4PSGaqmDYy>tJ^pZQ9DR#X4fwWDIwQ=uLrv^ncQ?w}5 zPOhBu{`0q|>Vxf^^DVDVd%a#jQ&*G3UD<0)#2oCb+rD{=zSU|!VIZ_yA8iq^Mh5}0 zwS@{Dw4=(c!KszOBe4(BQL>(*bvq$vdqq9;oK^Hoc=Zwq-KM;6H;f@R%HMZs*gVj` z(&7}pvQdueU*VTd_W{&%#9cR&YPzE^6hmeZz6PcrcFOlwnAC9$$+CEG7-)XY|ETKo z-=2WZ34cBO>Dspebjk=eAz5?h+)p~O)Em$&O?voL;Vm)pXIpKdMvfe10=}nYv zy!pVo`BH_3c3m-9C;uw}cGGA3eueH|E=O_QAWIJb))@7o-P8?aGirR>^~AWVNEQ6! zE^R*=b?Z*p_H)D_j0v0a+h(Vx9GS}3F?fl<$?n4|qraa?y_zIwvJFa*pPCqJNb;i1)S z?v)2pPuPCk4zA=o5CR|;#c8#2;KKvU<|F(M%Xu(*sLMD{sTN?N_zB6I^ArC#uJl=j zm&0(B#^HVHSD9zqjoC!#S!^OEL>(B;_ZyfHUT4`9r)t8XtomEJ33V(6P5l495#fvu zL&Fo*l>$lUW^DbZ@{ONFWnc{gf0*BsaJ~*5mpvvoOjtWqJ-1Me-v}K>NPgyv?VBt6 zsrpnX%ikF>0-Rv6bRG~o%p}j(clSL{`;_z&yYs)Ux?jKUqAoG-LWlYCL)v7wJL3ue zq6l=+YmiJ`1YEj&0b$(`OpKm2MOt6z!0m4k=OdDp->7bPvQ0R)Z1$-A%OBoiiKvW~ z<(tooi9ie(PUz|-38FN)a**ol=x9r`Q70{w9NYUVVNTBN?Q^zr9=+>WGk(Ol*>6AI zILcFn{M70|D@zhzv=<9wr=0DJ1&_u4b|-$SLJ@B}VTH;q2~BOo8N1JcIBxHF6~j<5 zTwAOn!07|7-6S%VC-{KUFSC z72oh*8NBPeW0i&1nYqqvgvv6OgpqPHlju9v$}9o=typZD37nRX?M5@j7TVIU*L#9_ zy7n4^Ib|<<@4=yaPfmLRLAZO|U15C1O&gB)mba=K2{tja!@G8l zB?5~7F$;8!Kp;L$#Fg{zbU2@lUiD|6Vu^r28MJ8D)}>KKfDS~+Mf;mK>O+lzRE%jT zd%2tTkOt;2()*9n(brIFPmFrIJ_I$96wtwy9s4H}{9{ecAlL?V)ihbvLz@>fic6{b z*#MEXYqwb-W{tXpQ)MV%2*57-{%l5sQ(!r zPFdvO1Jif;weT|0F#G-O6aG`VuYFzv#O-{)w!u@FFVd9-`~De=yRMg3E@9zEHwXgT zt8&jbUR?|$L;Be@-KRP3s=z9pTY3`xFOofa2BJVKpuD1b06>2JnN8#sLrfdAIw;CP z&Q+<<@lNZ+?#~U{JNi#vl4$Q+c9H4>*?6~wEoN5d`5to(kE9ffux%ofJK1>I5A5yo zx4j3KQ3FI%k_2{kkT7}QOsSd7+vU4e z*w_=BQ~FfIG)Gn&diy?k1}ZEpQD&rQHC#Wx{32p&XcNw z+d6dny1ZN<<>@7VfSd738V|Y{NIkay80~>w8gY}Q$&($q3!eVmoG>{CL)nldwZMS# zCV$jb)(o$|7Z zEgRD*g~t0#xY`@)pD=iq*n25a9iENtg+{YVJG|GWr9PLsv3+?B0P6TM3gE>Znl{)F zS~-FBSnE-8S(X98NT9%BRL{$=nyN2#K%Ys9L6o$*Tf`ks_8~)(K*SX6W$!a`M(Z%- zr>wD8?4i5%Y%)7+Cf>wW`pDXiZzOjPvY;+-evLwilal^Z;<>6W`9`^83>#85Y5Uv3 zrKuN{03ivtu->yL>pz?&7T$EA@e|8LsxSy2VV@I>F6J0*lwHwu>Snq)F2+7-fv&o=2QXUkx!X)TW$hi z{W{$6kA#;Dke{DPRF#vd4&bkfUat~ReD!CQ9PjU&SN)69Qr^O(T-TbJF2g2VDneGy z)Xa&9cdb0~U0pK451XB5-_-ApzZDW1N&?LfVH|ploOxavOy1FW28W@em6QrfD8B%o zVp6Bun$T<^Z&dkY!8c#1j)6cT;gdtAS>~L%@r`)La-00Hn?g~HDy{naE+Dk zdpw9z^ZHGmOe?{>+o_T(s;P`?tza+QlH1QZjsZ^+jj2vs=9zWA7G8NwxYTCL-5nfm zr*J;g9%nFQ#`(I`j!D|^qzP~)v4IdGTIoi>Gm>Iys`9iw|LXl1#h^Jw85c&0(CZeT zmW@DnsV}n1JfiePVUasY|4^8QYosn$P4fWU77=A@L*>n+KUny|(#GnSI-l>cIOSbB zq`rz@72(JhLMX@SPs0?3laj)2)z$%VdHZUsv>A|Tul>@T+E%-i#+xaB8f6adL%1yo z$&Z(|xAbQpF;lEc@Dze9BN<)&P|VfdmsOuhKJLzF&kf_iV=xh|)Pp;K&~Dz02$8T8FD!J2!-J`=Eb^Rg+gRj-3hV8roEwRj^#SuC{MlN@pgWl$h9G4KH zEmY{ql=tw=Odi~3aMZ)!zdp`x%R}yPzywrH$md2^KF!v{PKkV6?P~}tDk^7O2U@u+ z53db@No()xGVK~hnuPJ3bx;?BRDz>AtmgD>%kOT^xZ#4y+x0sFpdg;oy8S?`5KaE7 z8wJK;`pnh2oXeFrh)Mp(gtk!5LG~{mApqJ^8aiFDPnq0z^)V&KOqJ(();xdnB9lGC zMQx_SZ&!z}(yDUBNge&U9qhWMWcW-gqd<(dO>(hZ4f+p}v?+{0e0y-mWvu!2RvhHR9F*?(c%4 zK+7w$T*9gQs^%t+=VsJG4mwd)KSnfFGgC0}!}}GO3PxA5T%G@2zpd+s&L%5>_JEEm zA0wcO$|>n>Hvj${H2jj_^#MVc(*?v-`@MKgZw%a{41X2hsJE6Wa;M-jvq|ZBne3?|Xx@=+pM%6fdU?AYSd9bAI+m zT=Ui3(v*&1zkc)bdc!Z5B}O?NJLq%rUuROHVv?~bva5;Zvy7Jp`m>7b5<1% z8!`Qva+RJ8-J}(vxu%Rc-3E$oCB?@f2y<1y_yZvphOX8g&081;(}ypzag`PLC)8Qp zUO*IHn*CGqxBvWVWtQ5g|NULG8CoUWBQW-iZrF2|YSdVuZNvzSwGzojl|9Y`PpptU zRa$-A*Vn*9dK|ai!ZraDq|?=(I@U>~Os2vnY`9e3Y2>6X{lsK+ybJ~!OI!Phj3&Qv zo#-_(@P>5~5uVO2B!tcU@w|7fKsP&++6i3T-F zRCsaJhE_wIjEvte?f|b!s&WV1rb02&v*ye+hHQ=BfqH)_`h{@8F|T za?B=qex^UhVZXg5M%GX>mjb1YCs?%{zgO}4@?bWvy|Hgj_3B878tkDaISqjQ;`3OY z=}WkV2qJx=BTgVC1Jc2aFRTX<`c#=X?W{w}CMZnA?=j_6^AZMl)GccyLeb4eo7H@3 z5`zBBnRDd_P!mxNvw6Fs(~ye1VY5ygI;o53ke(}mLk}s>s3Eqvg`p{4UWSHBfyB&S zUKsKXP-?;YSDlVIhSI^{+{5>RVb0f|b7l9lam|WKs8jb6(Tz7^aLbj809J+NzxlBG zkLPr>QU?TvXnOxCkg~1`cd6j_ z!?*Nvs!DfeB>FuXW9NV7Y~9F18@_db>m-g_q`Dj5ObWIU*S?)IGUTrpX;DJRn*1*X zYXm1ni%~-6j1ewl3P~~ac(iOzH{aFutdeM{A!mA!ZabAz1l8 z36;EwmN>&J8v3LMqhzoSEG4a9MoHr*%rga2LmisI4Z%)*AMnyb@UOJII0eis-RDXn z=5=G*y@6PUs>*KGPQ%lnBZt@gZS8By@(=(O`fS{F+slzR3AdvqNPCR{I>C`YDqj7n}aLU}w4ZOYMgxl|L4EE>51!2J)rqD zLn}W5n#bm*KIEe%|BC`1=`T`d?|leL{0dnS{@{I0mPG;taZNP{(9z47c3w`Na$Sb| zLE}H#Bg#xrk5Oi7s515zfC=WlQepiz7(3f!04kN!Pv{{56x!plHh|6b|7TzQKY~)! zKe7(sQqNT`A0sw`wF+{W;Ycd2o|ZVP1LZQCv(h|M730|7%R&YuolkKz)|av_6^_WL zN`Vd=M_Yc^`i59_WDqOuwbl7xzG8&u|nFB&M?RwF&G?);Idu_Ra1}!zP6eJt6g+ z<0nesp;xdWnY4|(PN`0@gHDkO8oA-G$a7;uj7W2`3Jgo>WSr5_F`Qn25%O=$P!&YN zxEUVYnmZo0xKRRZ#p~Y@2*kG4UUv27IR{bpspc8%q&m++5XMd-0jnd;@f8*_tWPG zPB6u0xdmiZ(f8enN_2|Yf8OyG1~BR9M~%Tf0n1rbV*(an7S{E5#_L+6DXpJ^++^&SA8S}Yp%xBv za2AJD8Ocnz#?zCspr<6KgnwqI^16k$cB}TTkHh1dWQRSKuAlEfq3UZWII2V1bRZ&% z#~T0}c7kxt9SZbDD~{o}@~njO`80vgqMN9i?TeCH79Ljf(^IOupJa%KVFli9JWK+B zSr#X>NMoE00(nku=KX9+Z&xb1Il`x|F?G@N4(ixuH>6Kz`tmcIKtIhTDM3PagsrZ@ zLK`ub&9{yP25FFR%|!1LJ$xeD&J5Qah}CyulueiAIVCep3gZ%;!A+o0FrxffyL;2G z6k8XDS4+%xJB69|x|HX>YLs0wnAc|6JPom1+pWuiw_PNZ*wYiTBA32zhG)?C^*ZBI z?gfR*cjI?+GRa$>py?4T5&d(Be1f8rQ8D6 z4B4FuK5pnI zLgAY^P2#W4DtF%*vfy0_q{t%_hpxn_lYXa#yYHKZ}+={})ThH~5-70inm>b3FFY%jjty!V9$g;!MF zohV;6_Bo>Jv<(8%vG)6dH;1R6FzQdm#k;~LT)%jI)I6h)DVcZ!BelG5^N%t zdrVJ*O>$TwA}#Ru8Xb)>R%X(rktb(QA}XD2w+MKVdH)Gvf_?yil$-RQ-tet(=`X>Nj^X6<|zmjg%nO zG!ThhBp{>1)}Ngjc31nkaMoI}lrRT>Z9?I#+*0w13@}%!J_2OOl(m^Z(!&~c2&pe` zdazIni7N{#mgy+_Lx}P~b25qpw1?ci&6t*!C{>0~+0r;+9^oq!wK!Fjl`CdNjaGeGbF*>d*w3alBjpM5 z6nfDDB>?)uG%2su6ZyaO;s;{}0-YYq;j)8eKo(;G`Le8>`E5&m#f}IaGgAr7Wm&ZV zwq#?06GY%L0I+(o9wmS<$!6GWD+!#%@LLRHR#9j5kR<&WPqYpd=_B(&+oB3Dgb5xL zd1kcUqs?4Zrx2H!6I;o9G8{Z-^cs2oo2n~pWA?WMP|}=FaV8NEWr$sdw^pdEltiUz zKiX(N-IeWs(%r$&hs>a^zjOA@ou(NxQz)!-T7G#1(Pkq|tX7oT62rAS;(1klX? z)fSc|Rz|K*Yd>3bR;@NyHn^q=FQ|+?ZmSv!(tfg3Hs!a?p<#Mt#=j_c^) zmF7P>20AxXsXieSP2N(1uRl6`5m``Hd*L2t0R$>JG(oi%k3!+_(*12F{qEyTB;Fj$+awcU5On>S7GITL zXWob`;KbW69$5FgbE?70P2O@pydwp+mF#&Y7MF=OKfO71%4TNvgTjEe9;Z&x?fi<= zHQl7|#bsx62J?J{=OA zfsQaYHz{^}WLZR~$d6ZZW0%sIPXbyvAG2JSd$&@S9_URgvRZ$LfZj~?E>%_iZaf=>hK*cS`F7 z&Pz#0(U0nDq0ggC>!}~C8(;I=lccol{LXXB{JHfu|3giD8w6r(o-|F|TKbX9u2%#? zj1;95cDPyap^6;u4l=_@5fkaBnPKMPjIOEmGlQ2MWd-^o`lPlb97gn9v?7AE|4md- zox^_N0i#SOya1{OwPta%Y;}%gZ;bD*tJpc0DB}bkyu6Aux75o~fl;l)RqL@Ckqjld zy|3NwIIdNSIDDNGbYi!{8H_K1VsycJo^wSQPzx!?an_N`4k}vH)|BJ$qWN?IyRecPVmsEf}MvCYi2H4g?wRK?8&o@db&r-v4Dpmoo zlUc)F=hY`(Q5>9*GAqw=IIjewpWZ+WY-jA5H(hY02ZnH5iSc6|U0fiRREcywTyoBVx0rb1nTA0QwTAsr#`duGwq4EgE<0%Kss;wNC=YW zv8#u4X(r+D%1O!c)do-DhS9$JoyC}B3@OqTP3#7Hr5>$nE zez)S+7A!OSnU$0fe{wJyQm1avtx(@^}e7z0CB+B+k@{y2+Kty*0&hfpLyTG*a9CVyiJ2`&`OKFKaQvi zdmoDXKuC3ymyb7Nf4HM+T=%cyEyO-t#2E>6hZk9MQYPduAhG+raq_3trxel52xTfC8lrjEwHEG7F0AhlAlA`@cm=y&Di+s7q=7Z%<>uZ=QSp%^_F#H zL>t!fMQgvE2FluwzREju7NI+LB)uRGBI=qRX_&Tyk|#=;2Q%4qt)o(K&;}N_!bHKm z9Ti>G%leBS!AGB-unuDtqy}oAaQELi)~~5#)QX&@2gM}TdEgKv@Gy@uKy^&MAktIGg|b%b>F;f ztK$0cGXU9Tor+J*e$dfb)@||@>HMH>#MI!y-d_G7l15Uib|B0doQlpfB-MS6go`F* zL^I3@U2wjx^>lp`1UXshM2;dzX8jWo1!WaBS^x>`IOLeZ?8X>_YX#R`>UPcJ+ln=8iEAqA*^9YG|EhzU=hFa<$IfX5lA5p*h#Y0N^ z_LW8a7g9)VNzCrIUeXRIBCA3fn_9dCGKv7k*5It>3d@N9fH*YVHi${5rW2~Q{47c- zeEAJ9Psg(1FWn}}A5_X8)pWO`f_2P?e-x%V*==EN+Bosb_O%CnnX#N2VC}*ob+-_e zJ=Ll#K}S^mA^*91?CH0W1yOgQxn(eY9?nL0{k^zairog#l)3jq)n(i0%%&fg^F}K8 zcsIQoBrrnXzjND)>dFlH8PKt>Is#jfGdpeoTnFZj^c9D}!c- zq@11MyYXJGmPChdlM1TnKbeUqC=@N{Pt@lcuZ5f%9FD%u)3Py#IJXo?GOjxT%E9x3 zt-Y-aUJ8C*3UJ++?DRojwcx%fB9M)7(3Dx;f2bT;f2HukXGW%_ywMO&z;Ra#rRiEO^!A4s);uPTB!m zEwyVvwSM=oDG*q~e|lmyXRi`Usv2{Y*zoLcNt}7IoggKs``kehY%W8IF+1N@T#@PH zVmQ!_syZ_``>}v9+aN$EQR$eFAjO|@mpB-A%v)Z3n!?7Ut^+ymOD1XQ&^ngdYSZCH z-VQ%>a?1#ulX#bKw33Ey8!l`@73=u*_6+IU7)rud;G5) z*$n48-uz-b5M}F}?`@@kZbUUmXEFMkFjC)(dvs7R@1Y!*zFCTF76qu&g z>&B+mZffi71Y)!?^wWSL{j@hoLC!2^a+?RWS3)|4dF7{)lfMQa&Js(W)9yu4;}Y>) zp^KmO^d>lt8x(7-W2~|_%NBZYd30&I%ts5lh)%@YpET6h9^bnhk}Hbfv0QMzAFOG_ zG5L1UURvUHGVBPx(sq&r@{@qaCm=-{>NDtGxUty?xQFEhx$?OG@Wm;q2(p8aOzVu% zK$MAGU6f3hTos@VM++kAjTZU745w}PFgis&Ise`uKq#+#k!(^Zd0JahIAp)K@`&s( zcS=+{VMq?v_)4jrn{eq8JTE9M{)Y*|`;*7vg?&P2K)Q$zB53v4RX6=aA}2nO+CyT9 zg+_|W1pzOs1hNO>HlUh-9N(UXK}dsFUyKx9Jp4s@KKe3RYnr-?jbp>xwPsCO=&LJE z@pq?zTzF1$A~}xQx4vA*M+Tzlkcl?3U1*7l$5=|qvT~+)-348Eyh9I5WX-eti67?% zhj7j?EnS>aD_z>~O?6!+AIC$)1@a6CB zb-9+OHImg25jbAoe0EVy3m#TEM~6k^)qbZlZALZXyf3(DM^)6CTJ^M9N0!3)fQ;Ir z8E=*pj|n}a>6T+}OnBUD4NakV8Q&6RHzF^))3GFWd`275%GX-!w2A;G=f+LcpG(dx zCvnpQJ|Mi_?@U$Yzn>=y_|*HwTAm3P$+)IbYBj5x*BV6NFiv86uP3_CNx3}@C%AvF zX}&ESP%=>WR(4-172F0n>LeT8*TIgA&W^`PM?7uw@jR z0p==?wdZ9>8|?wM5%~L$YF;xRkFhaSXbo+!J4eMH94Ozto+ywEesI+m_!uA_s(cAocaLS_xtPNrw)JQC2JRgOns+9dj+55wI-Y)#Ur0Jc4VEe~m4&NFl=lNRM zad<+}o4|n|S2L%&@Z>eck^h1};m2PNXy^bXl>saWX<}$W=ehV8RC9lW5)cjBC`$ez z#&+{Oy!cFVQ-*!V!8<@IUf-qZLqDf>-tA;StFk<=e>$|^1VosD|qZt8EB5D&xz)148V(3J3w{!8wv(|b%@<%j**fAoDx_Hq?`v#omCDh*= zk5k*UE5+J>16W!J7T@!ib?uD|i`Ujt@xAN0u1!q2hfs&wV$0aE$Si-J9~>l@B?lQH zrOh8y_O3kJpl>vO2L}(#-7`g4v>$!QKrjswQd4791W`)KXrlbN43 zUj$~l5?hd}=+s$EXg}NX`6gB~3OKQ8)_)k;*RU^k>A`U{q3m09YXi7qRqxLJfK64g zJK}Xkv)CI~&hJ;Ark-N6^r4;XuiXGbcJ223;bUHQwF5Ib)G${8DD0IMzvS?Jr5HTB z&yjUpsuP_{`9Y%F-FbEE1j_;HSs5_IsOL^APf@3CTw5 z!L^v-3)tb3>0;%yYka1$Pa*FR-MKKCGy(|;D7@n>$td_Fw?1=nu(;f7Ny|qczilN3z|fce`q5`Rs;TuYO%F zcsS{f2-$x=Wt#41b%{l3GT{yJ(^^R-xacCwZ!-UjQWT;b>Jsh+WqR5^f)AlyasNe0 zvd}4E`XhU)Q(nL(>6foxmw1N{JgIRFdnFiWu8CqQ;)@x4fezXK}EEvt{Uhc&)cTKwPf@B6=_;m_#cvE%Vh-H|DP&S-nU zxaTe?30#;ijBR4keuGj6uIuc}lh)8OXJXl+RuH^&p}>)S`WP=H?&&?4JhZ_pCGQK7F}nR)E!=vTc%+B7}7 zldjufCg^J^aCwoqUAokGTH1TPFXnqq1VCx7+b+X2Y^k9O%)?d*P0Solnd^Y4-3jvh zzyH4XSrO1#f>Lu2b3JVdK(Z`D17Nd9fo`-RYK<$`K+h1?Sn=uXT|q@@8liJ;-SU}l zIzR~6?CeB7W&w(4H)4COo%@d^gTL*G*%RA88okW2U+FC>3v4bwsmL!4oYbV|-&^zi zY}dY;yXui5QL>TG?0I`GJzIb5`W{&7w;njT_8NWgM2DIu=C|Q*)fhD8c6M`#v&tTs zLIxf`y(=t9UM2C?c8gVqys+xgtf%(Y$vB>Uc*f)*iYl5TKJZJHMMU3Mo6PN49uKs% zBf%y}*-kSv@^2x$3g1iZXeGs;b8qCH;dEf3ES>6q*ff|{n|%$`4=w6^LZ^MTKKu@c ze&im-i{s_gE_%s5$6^CT#>ZZx%+YYnJx*mC4&o}w1c7qYf7~!WA4xZ(^c0HvD=pX$C%itGtZsWn&KW37djQpOA;s)1sc zKesR-R8{dHn2>0^W*y$hUltOIi+7Aj?IkEqawINeVqz>RNQO-}yYIfrI_7JU841a{ zG&i>>Ld(#(ao2Y~-^wu8B08|SU(5nVkJfuc(vFt%sn3>FYCTU87XRkVV7B8aw*~C= zDtaRyL{jimOg4tYKB0#Hqu2=914RIKdP-AVqC*ZxL$$PVq;;wPMy}$BWQtrowFq5- z+Y(CJ;v0X~DfI!-6jtb&j_~1jZoRQEj&?p*>dwE*Gt>ul{HCv#Cyzl&r!}|%RmI_u zU(=J<)N!PQnrR6&x8W(V^KDq}=gOTU-b*>+B}N+JB`&-U4M{DMFa18nSS;O4Meb9g z@vGs-)bxFD%#+VIaK*lH5 znkehs>=HpKKKieo>%s%bC^14;2PJa)lC|M+x(#mQj@iob;@L4F4%11Hj?cSl#}4TR zVsWlNQ|{74Q+4u#Suq)xJ;j++DJhxiexN0g#LNtf^A`U}wlq9iuM3`t+eeIG0T(|# z>d$(3L!41dB@3BVkl>mTcEs`V)}CUj|ACSa)II}emP|7X&K%`5Y6(`dr~s#XO7UQ( zbR9b`QO_i6SK*VT=dT5UVEL(sx#yg({i>HHYMbh<*wyr4{>Us-c4Sulnh0vBgK&up znFsD3zf{V7#7Ho0c3+rE3NQO~;~nWGs*BN-^um;`$Yaiv!|Z=zkW03FrK?Nw@A)0U zcw+!}?bG)>+YE(BFNNfcd|jRAHr&Fi)4^OI_~uK7dPZvE>n8f#dK*s>PcId_94;cG z>#$ysJ;Lc*_yNb(l$G@8=MvSk$H|BoeWT%NJHL;NmFnzc%_k$;%1p{v)V)0afa@{& zTrE`{e-?gj?{sFc35!M+#y$N^4|N#rs(%yx^S$e=zp6t=sKcvJ2Z>-A&F`hd3Erh~ z_h#Rw%aYE_omwhvt+#?g#Sm+TWA1NG&?c11+Q4iU@4&Vwtnv2w34}42+BRNRU=7CDfZAN#c_tHXcw_n{nTS zeXasU9=f~h#!g5?xjpU#tb>ZW&|Yyh0z$&C^i_Qi+16j9E=md012{J;?a#iLYaN-mDbLPGsq^f2hHq{1$O_+^n3ijBbB>Pw zD<@Z{Ei(HiaMb^IR(-{_dqzN#8_rwe8kWYz4?R!_Hk-Uj_KvRBt3Oh&JPT=|6-vbbUgO67 z9`+aIb3E(^+Q;dZ7A#oeU2e*~wCsM=<-5?bxl7H5BbBJWH8*6`vmaznT)S>N6M{L zNdGLx5XP`~=_k6}2f5>g0khkG{`)X-n?0hMx&!%fK>Pgw|70h0Aotw*FUqu#>-|`> zjQ0s;Q~2BU5GD_u_Y?64a?Pid;bXU1Uw+0O7Z(HgFsglLg^B#p&eKV>Tl&z`d}-<0 zo`^GAoLeRn-LF*#2RyuHEuO!|=C3;bec8CLduKO}gK?)iB8d1SucK3lueuiXJ!~jr znQH}jETM68q62uJebqOyj`;(RkO_CP9R40~v084PJ@2Z`%Bs)c95S)dw#Q zzqh=n`}|qetCM-}@aoXE19xz}fx->H7zp|S`OMR3Z@Qiy%=eDM7He;WqDc3&KXHnC z=VIy`vy>ajQyN7jVB1MEy#s=HHuH*E*9Nov;vgL(*)a{=3rOULnP-@!e>S!?E0uo{ zFuJ8&Sim)nsUX*1775d;5jyS`wd3))b%$k0S>o2`5GcFQD_t_!U zKJKH+OT0DfK%2NqRCr71yT;*lFB!KOzo~K}n3cINPH$`Ux@A}>$hdgS<72=@p1jZ0 zf3Y!Oq>x~~XX(p)PO!E|iLG}1X4to40SHlyM)roz;c^=O(Op6}zcPI@mjZQr$#Kbs z#v^GzHNV5wcV^LeM7u49>?Y_dgDS2SkDfe$f-E4W&DXws>-+x!uRu`09wvUsZ3fjj znYt`n-Ip$WXsqT^QF}&QuF{wqUEJmpnNp=pN;D@rV@yh$X>nN8sC10@jwScfu232f zWoK#G3zy*0G+{Jfww594y=2W9rwMff@23}*CQ4*qQMB`SY zQxdz%BU2KIONpSl*IZP#ELIF=9cooY#NJd%OPEZMVK&tv6mQHcY^HCW#xg-}eudjr z+6cg_eikv49e=_Kpt<;WiI2KCIG68)*;JrH5ZMZ0N;>8%cT1IWt4e20D@;OOoXpJ1 zVWC8$Td7Sh47|mcqcY=omou2m+eGI@<~pn@R7jIIDqIsn<<4}hOvb8gYJ`fpbp&l6 zyTq#`e$zbXq}YaU!z4}WM@zt;#n<=7`THk67(CvL5N+m~f3~KsIjKq6Nh)S7e((w|)kW$AN2yGX)F^3To-V|VwQ-_izmHeD} ziT(-I1U3QH;Fm>gzjam#!OW5txZ({&JWbI@N}Nh%Y1Rj6w8YG)8dSMzO!_e~rw*8k zTm*ZfBffK%mn|q9nVh z%hM92u_uI}gG_SP&|nWT-0nHFME6EomN_8zO7p?-69S*`LZZ7e3Y8ssW&5IH_$8W` z!5js6oP=D!vMN-mjSABBQK@;B#^OwCn3EF`hHn;{F*69thSH^963nxP4(%@4P3)#0 z;)OrOKll@O@lXClZr}8Y@{c&o+eCV4@eD>@Ag0>DmT2ijsakx?)d^XaT)E~_Bq6PJ zl^H{EhqM$4OsoPvQYDgCx`Sf?7mj3Cds`>?9EbwgKIaeZ7tY)s=v-ofmR`F>L5k&z zb7&U{S|qGWf?QpA^c^F)t#{E&h>YG^virxF5{ck~V%SX1UL1qvAUmglXGBgVyDxPK z+M`a_t(&MrC)kzl&cKZeINpS+jWM9VtpN=AhU``5Q)eGoU4cByGEv6RI}^P#Xl!D{ zTvJ=g{v!5%sTAeK;;@5d;?S;oYl}|W>JrZH<^{d+m=;u`J|Qi(IOzo6LqN=}3))sD zAue1P(=dm5dNremnC|ePX_pvdSv}G^q0|_)Z0C^@zreA8e8Aki;T5gE>a-Do ziYXy=X7a>KyEvBTTt-&8(IFR2r++|bYZnyrFGovW($>}w?ksCpJIor|byWfjN7W@5 ze~3T|w>GJYe4{LB2M*C08pUEbbI*8#YHo@yWyamuuPo~^ePY?2$={D!V4XuVVPHI` zSCwqOvl$l5J`gv_c7l!%PG-m0P|ObeL2}C96`>Yh_Yo|W|#H_;`OTsC7 zUUYYvgRNN&8>mXm%oy!15~fC?B1&Q#M5%1)E1_lbZ53zTG_=kb=9bYRx>JGtuJ0B#h)^WOVO!u646NQJ4(#Eu`+2W+4kM~ z63_9^yf7@gE>7X28xIl?;eQJUgA4TMNtY>JehQOL|a?Zr9fpuR+Ov_ zE;6)dGNVLZV#~yC9O5szaP+YE-dOoI6$tg*`o-%>vhF7U4+LG$>m9ol<|&!B-U(5Kf zsfXgK>CcOosdbx#BBw(Wc!yoG&brj+)RLuW`W-G`u(IOE(RyQ4-_ViP-70es!jT%X zazntEsfKMcduJgwCzSe55PNU*f^unaVNNv=SO%rZ4|iFM97S8XQjIT8bmml`CC+uI&4}R^E@PwF z7!ZA6#{yJd>}A%S)(5o9fjBW9ZA5Oy()4tzl`2$1X7u87F{SH?omWTEpH1o3GNv`Q zv}Nj^iqOzTVvNoWGa9~7O`nK>DLov0P-!|R9}M4DYT7u^CY(l8MhL{tk6H0Hm!+2i zR*5eWR`Ec|(Gl)+jAZu@A|wulJNJ$vXZQ6y7&m%LVUBvD`);Tph9WDwXv(YOTzVa} z2r)@^YHboIOVGSW6Lx}RYL2a-5!e{|qU1+Y^1$dZyLguPg|QM_>|%4SbX_gduU%p&x*H4`$) ziBhz^5QCuYF#))cj^cA@rXqV{C3|SBb1cG!FRXMY@7jy7-C#DOe(`;>fI|LCfDRuK z8)I-Iwv@ni4Hi~3ZoXl29LGZ#3$*!wIGnTR91ePJ(ivDq6&iA5Innd!G~>})+_}~< zeH_A6eV5u{IP~IMp%~~IRIN#HX47VAgbCIdE?S!-R`->S@@JAIDyJtW#6-z1WD@l2 zDpgEWd6fn_m2ruqZir!@MNF%bAm`f<;tgKe2oYF~F_`KP zXtw7-q64We5tpS+qovElQAk?i5%LTm*yQN=Ah<@#;qeePheBQ2ip$Z{iBQuXn~c5X z`rk(}i>7^9*5%8W7fbdp(|(1zi|D^o>Zww- zEvb0p2QL?)(5Zy1%9Ro)q15%7{Ke9udTHhl?nF)tFd8F7~sjNH_J2bjKhY<_u_s*c&sr28} zf7idLGb8k3!H)Y_?OHmo;wYdV!YF0^ADFA?L7egNe~D#i_=4pHxW_`h2dcd|)r_M+ z(Ss1E;a21^TvnB4&zWi(<0$ziOr+T$qoABegd1CMwB4u3OQ8)B~8F~|mo70%>7raO;ZWTzc zd*`IFh#O))QBMGDnSx?Dj-~oB>BqHFpxX>ag>glob#eKFa61jY(Ek8{e>0VPf2tG} z;aT^&X7^=33GyNrA8~>bkHljQc$P5ujiqu%h6VSSP?ET!yhYrfMD4bzWwkydZPGo> zT_VhnFv16I!X?3dh__?J8##_CiBE6czsv~ix1;!lwIP*a0e$oV{6y%NLd8pd5s5aQ zNIl4+Ix|t%ohw%K8F848#Ho6*PGf~Lf8G&|W=AV4v~r%Cx$MW0CHaEEgN+846GCkZ zm0lsdyhO&17|!zMT^M7cZNyOL+89T4e32S|d|8(RD-;Do?;mQgO6i^kD{HY)7TxAx zbdG@>LfJ%F#--kieHQSyX=r?$_<(!0{ivkV|CT z@IVirbMzrLQ^0*#^9j|r?J;XP1`{Cfn_O*u5ty~%ZQ?a(-g1-XZcaMwOP%ABGWsoZ z6d9u07lWuR3&mcNoU?}+q5Wq(`of(3<=_K@e$f6dl~#k>6KrAOZ27rg6VxsCme--0 zqx>^2QmUQY1|+iZN>tj3o$4whKVnyOYBQE87af zL`ITckC+m(4Wq4GSGyr$$t=T(aW_fpI9?ZTyhW!rP<7W#QU8yB5uAJ8JJPJNh40K{3ol z7^hbU-dc<3end@HJfCQ(lpZjBphp7lnM1TD-}aZ8{bu;0p0hr7jhoB$o>JO2Xx%L> zE?l{6wp_gkxp8l3vwT-sd@xZCW3*1hAl<=6504g9aURB}{2iE;xhMT1MD~ zLwzWBjOOlzTjW%aw2_APgR|Bn3Qhk2dq9XkUUiG@8Uh+3Uj_+lp#xu-5p`WLFB&Zq zc!}UTz9rZH0O4GGWffC!T8i_nO4Y3{T)$H)5snX-XtS)T^<%V9;N$AUel)+SmaKPn|AY6uc1!)r*xF7Q-k2h2gn}Bld#8aD;4DfB2ii zvxLYs^f8J;EQu-gai(-g=<9GqP&ZJ(2Es7_lPiA_@+g~|ktxXsPBe6)Q{pActv1rL z0%D;}r%|pWjE6CbeWwIzAi%1#)(~mm_pA^tVl1f2FSJ^S3O5D>9We^hqB5}*t#cAw zS%jJl$lsJAXGxyZCa$9es$60K)4XkgzRW;?9R~jU%g+3- z!I-j+va)Xj8f%>{TP|LNseYTx!v?Fw3lQvZ+%N#}ZROHem^p)5W>&6a(jz>_MVW|^ zFAR2MSZ}0ju@zJ81xiLyfFJ#%`_hO5EH+*%VHV$LC>yZS-lLw8%qH*xULnl2#4!}P zf?_1N?GtFdCUI!pW)Q#k2~+~SugpZOTY6LsO3XIUi#XNvhS4k$;D=FEvrcqB*u)p` zk@lN4WZ0TJVp^#k`HJ%!@h>v;Llc>Q_?IqRH_>AA1*3ndef<(uy^X+P!S zT~fXy;odG7ViKijXu@V%Y0j0UXe@M7Og3d%Cefci@;i{rTjkX3&NDcMNNIAq%F!dV z2;rPTz2Y}HmrR_A^}7{Q?G*YQV9*rq9Dei#!y(UW`Hs@p+BOF8DDI8|>MipPTzbO3Xbu^oX%bFyTL_z6kUS$o(4;e%s}_ zP(XB!Hlw}cRYh3K)u_Mt$EM)Ri!d&9Rm2Qz^D0db^BiEOt6m-AY`z8Y6=ga_G~?6a z65-oal{V4fs%+^3c}?xr8BnE{`JG~fWtTBAp&jmAVi%@kU5?Z;GL-VOuWzzKjBG1?0bE?nLbG^xL_Y*-KlU8XPd{#F34(Wrs|Eod zWQalovjkQfOleVl7&NU&x-m%~JQ2;8{{WmO5IN-enPZrj&`XTDan`k{3YQ52xZJ!F z{O`{5&k-fO?7AE?3vrg@TuZ$!T)+KsWyB+h+A&crQ+7oA%nMgJSHx1P&KB2)kC^C# zO&gBT^qMlY8jK<+n@YWQ*9Y!)GLD`jt1`BF&UD;_tj02>Vh~dna|n?m2dcWtFJ*|0 zKFGy;4F3QWRvtfi#{>xYZtBO`trM43f-*r>oXzKL14dqqxXa%~7*z)9ow=7U`InlvJruxMn3Xw&w~sj`kRc`zVB#;nzBmEBA#E24!|M=dl%EWz`TIV`Hqd zwTQODR&{k<^yY7+=-$(#{`av4k(K<=xinN|n6r3u=(%#^=@Sszj#p#L8^ihS{@_11 z{v%e{=^M-jV;3%O{{Y}_TM9+^kGRw&o0MRCGYD4)@jS%^(Y`4>kPb-cbDu@Brx2Su zTw#FGy{Xz$z&kPh&487;xWP>?M)#M9M2R_!Zstjb1xHsmg7Ef^iL(1c8)z%H4qv=0 zLijycr7eAswmYtd@JkqMMn&Fp7v7Eqp=54^)Fv?1e*qD4cnbC)5 zaXT?r>CTs-tzvT&hS(VEIu;2Tje;{f(*3~L0$eWjUh^yXG2X}*q*XED%YmZnsR^8Wy-S#5p? zX_lj|oi*z-F|Ku}tBCRWO93{Te4 zghXR+vGLL!V$813?lY3TgIvm+#eqD+U0~loxn+HL?n*|OTy#u9jJ*PF2v3OAbY;X8 zirDr@uVt3?=@^A0U%RE2Ru|ebT(R~_fdNj8$7=I4phgVd6h$3UZ5Y(b;wY;hpS#*V zYi4*Ih=VfEPWm$aCCVT&sVnSv`ROT;76uQAQ9#<`g<`i6FaH1rm@bpF&iZ$gx}DVS zr*-t5#6{VULR+M#{{RBu!Ag9}7QW^=6$1$5nA6~4{vKu+#}8i-!%LN;2us8zPG(@B z#HI|R7$;ERa5|9?Bqhv1&Xv5Z#EBR%SdzJbdO-(woJOhkToK6&bQ9OSPu;M#62{lS z>k)J8se}yNA3|=zX~u?(2+EZ*Oc9qYS|y?X0IOcn5Cg&3h%&AXN|mO4IF|?)Hnkh8 zfI71_M#M0G&SrO#S9^`6(G#v>yT&hD{Y62ziD7*r89tMC9!*Oh4N0>e1%=r0{{VO` z-1%=JS1r^0ftVd@&y1Q851i6FZN?D{8(L0+#`-Lc?>=F>UU47`K1jBldY~ z^BMLa%UrQHU!fGrB|ohe5?dX~U)0CW9!LVoEUZzo3qY)Fw1sK?!I~EIVfD5OeIQQq7a83uMZ6?fbT>9M5;8ZSi(J zmsCmzgKqOJVBU3x(woqX3X6HC}3z6iJ2^AaS<1`mHK*%U}AY65G9@6QLM|NFrS!Ui!;O)l^8t% z2xrVDY-RqjJzLKEqtNw|z+ z1qk$B}&JZ9Ys;l z=!1WG-wj!B(1=pA!Kc;%Nw+GwR)9F79RSL^)J)2z?6r+Gd5=|=UFGMTzg-uW>o0rD zadO>Y4)dsi^8wjl_h)V|Gj|T!ypS||X>YR>gk7}#Nr79g33Gq4)AyU3jAb6x`^C%? zw+Uv>lLsk-wq*b*+xUO9eh8Wup&vqM#Z0RLMMOaKHvZ$Af59Y1;?$1uB4SiCGcQKs zlin%s`4V6BzhwkTPFfl7C%V*PVEA! zF

X)WraLVS~#Xt({%{qB&|KnsLkhud`mk`@BYb*F~{-~8Ee>nj7_7-qAj$KQjgON9Ox=3#I^bV0I?ePVa>fe zc9f`Wji2`@guh9mO3?-=K_xf~za5ORK^4+}sYuP`!D;|gtzsog#+2<6^P#pGuX$^T z=swDZ;lgzx4p6oHe(Q$O?PLD{0oXm4FeA5CK9QZZG}}%g9Tpn#sdFuU{fG*-^NK|U zs#~o(TwNbPW@APStFT2mK1;CZMJA!W^B*iSM$T)Ca2b26YxluS%aOU&PyQkiVGq_SMML22bJ2RSHi zVTmuB%Pml0&SCfq-TaUaAIbF~2w(WbXgN_6^iG!sDKQ*DOr!A-r%mSx+oRqQyL+1} zqD2ngjv&GixUsDSWmJYlESQ{*o|;EF3oz(P70{Xt+L3w=)(Snqri+I?@LmG#CCp&Rib!@ z(lrlIF*5ELmu;AyV!N|;jiSrUB{I6q3|vl9;>^p$vRqvkEO*l2#*Cz5*ghr8E~ZI( z4H1M3nN@IX7CQ#YiR~)el>KALaO)4MEE#)$Mm3jQF$X8;?iB=P<*k#w*tv^M?&KX9 zNjKUEz60J%ivke8hW!)XiRBQ+GYe}wu)3D8_Kbk-t+h) zE#Mf(1i5!E*ur*~E?=g{X<~jt{2k?cxoqFN)rKhmMnzy|TTz$#BSJ8+Ivl zf-bYxTRn(n4~dbXm2&E>mo76GnR4_nz2f(lT(RZ=<}p?yq~E1d@XJVZDJd=jTqu{m zWx-&TH@r`o-dq+F%*;hKQpXLmlF(dKvS*}DtZ2BmEtf7*JEID9ylHaf&85qR()G@lT4a|tL`!Ad z7`TN(^UUFdxoAMfRxzCI0^TA47~YA_XE1%+b>WC!ToY)a{TTI^m2u#|2|{{39FPJ4 zitr!ti0mY-NE%*dD@G2GIsof1L2o}!`A1?>OMNcaIEOJaDjVsvu3KC{4j7)m(+3|0 zI=|@>zjQ*`bidUL>TJHUF;?+&t;SX(=|ztb z532M##-R9N<|hDj<~7SVb-&OlK`{`wRYIvFalzn@VjSi4IF%G)iDJ`n?6HRkv1L5M zF?eF16YP%-3hOUZMavh^PM)8Qd5-$oE?m7v1eYx9E?MR`aA40ULYpi@d`wN%!Kf9* zZwv-}0(N+tD-TNiJjX+MmHHA<66GH4aR8JYq}=Z@P!iDCf5pY3B|2k8%sgns($Tqa zbtdWTq_s~FZSVca*s)?7BsQ72^^Qq#A(_l!g_Jc2u}88E;mOp1wrYIw`=Wzv_z1j4 z!O-?exPr>7h^)NIhFF?$qlwI-1X)@I!)4HQdPa<-SE14>%A(sk$~3skj+|)9Ks4k$ zuS2}SAGaUoD{+h!%x5>5m7M8*_(7w2gs8k5o!Fsq92_5svH+IdW^N9O*%ogai!Lr> zNXoYs38?4Oi}kOhzeA>Dqor%atp-{dLE>=54+`kf=7?mxNgf;J334u`UY5|Czn1Bl4+aMqy> zv|Vqejwft4dJ>J$QgheJD&ZBnhLR4oW!aG%F`G%wF9m%BKOC=P^1*y}U;8lYBM zK*{(70&n2^%*BD$br#L-(b+FWtvN}Eq;r>;%Zp^WnOfd`7YEbEj2XPSdR~k)w)MGk z^tyu6WVyt+GU^N@hFEG;4_GE0;xAl7`Iwmdee0y#odnq;SG`&)j9@}ywyW_sI-u+R z=h-&B&e6uIRJ_iR({dMjR){!^*O&&#Fl_gf$XHm*ZW``s#3f4R4C~^2&bcM{ImdYW z8_5h2aA&tK-1<*pZ+C)jxUbj=(1S36P4}F_27^3<^9FMkTowYM4Z>P+DM&ej zG+9scBZs!`JcOzWedN$*1lxx#YiKM&_HtK8%ws_s=UqH>h--RWs7FN6871RSFw4HZ z2c%XKC^`H;#J!4GeX>=Bq`e{)oWb(;`Rk#X@KVZ;|~Yyi5;5U(t# z8PeeB$1JEvIxV#Z<|Q7T<)_VM)$biuyNh~OwG+?~Xc+439aur_dB4|4!)z)hM>6lM zW@Y=#P)*HWFFqg=f9<}}Jb+8=8)o?#S#co7Hb*IU7CKa^%q7d0>b*^G9_9KkONv8L z^ou}Y4d6x#dMS5`8;D*UTn6LV@8Rl98`}^4jC88&`U}j&ong3cN@mH1Z8ir5;eZuy zc@uH&M5~uO%Fy&PnORquny>n>Nemffegj`vTa+w%RRs2KAw|G!5Yzi!&=nc%@G|OCJS09XJt1xQlY7OVyR4 zlAu6UXV3}we=?fNGu|bOyvaJnsJ`(qTZDp^9sYYv^J%mAAoKz{h6wdVWs#Rs;^;1f zR%TpQL(%{Sx@-nA6g&H@+vv%3L`$%Qm!7~v(?jY&0jmjya z4TJJ`JIA$NWx_`(Q1hii3mDbQh9=r zWR1Ap#|YpuKe=pRwP0=`@;#6NyE+s;@fBSLmp9SIu8bfHwh3vva2!QYqw1_g(}~WZ za1a*>xIeAWmU60P&BsdfF1WWii|-v%F!P+x`(@y_k9+>6{$}v#EU2>FOL>eivgN^! zx0!O~`Y-jj7twO&Z&n-2s5$`#ZkL!aq#@0emmhW!!(_9`4`;d2ikU-qbD~B z#BmXUxHcAI%D4y3;pQ$4qlw!=*NsU@5G~>nXx_1`K}y_Uk+6a{wR}o3! z!v^&rr`R?(8-c(T=6>aXcx?P5iE3Ba7R0z3#BKa(#3mxZ?J}{<<6F_Ove#kDC{Beh zE7mGq=b2C<%v@tWV(88YZqly1*TF93=5M@YtR;)0`=KnG9HzksvAo@c!a>$O@0oh5 zcNn?6X*h_ANU>s)y(j+wGW`ab&{$Z5FEgDk5Hl#-nUX9@sNq8AfzV42{Hf|AFr@d$ zqXZ^pDcFg7lB;F#I=l3YMA_3ivh$;~sKhB5g1BF#XLwWg80bNRObT2ZAlVYMQ6kJ2 z}-9pk{2w zqG&jY5MJaIGS*`u>>-UKD()Ql=l39%2aeu&Ch*C9foxN7rX}}7Ov{xxoJKBZK7dRm zpfR7qOGg!B9V4tyLQ9Js127mlh4ANJy#D|a-Y`)uZ!XaLqFhE==Px~omQwt%5StY! zSBZRL`e?Y6T(nm*>qlt1T(|!KP+*gN3xvcNIfDpl3}+IPsl*5zZB@ASJI$vn(mP)9 zONxENL6?GXz()s+=9IqrzVvuQyCUYle{X{cj)p&q2gNn>fa3x!w+>~(5Hl_*D@%)ti#drJ z9bl>T9?;+P~mvIv15akl#l`Bg99m|)YIgBZI zgZ}{C1`Hz5DFTAivvQ!b^}f)9_djFaDqwN#j-!TLTJA%0)tF_Icl;0S3ayx)f(CR` zLNMXY*$@`Z1nD@n>9MI#Z5yB30 z`#wm>R)y%nA|8e!m$u2;8*hT>@Wge5W#SPzgNQ^TX-Uk=CI>JgrZ@Hp-!yoS?l5NX zorwDl#9ml-jgfCXVGRgIJ4DgmR);8L@O5S$%~yVALhZSmnSokdw&rRxco7QlGPnbo zR49>uK3K{Y0*@0rH%30-Tu9sX>)4LT_6IN`x4{*cZX#*Kc8qsMIc3Y2H;#w9QlOoQ z!&jMSDS8FP3klWKQ^VrP^~e4qKV-K2&GG!#;v;2lK1%!h`jBKdFg zKINJ}XpQ^ZKFk~Mu(aJOy1GSd_;Ah4Vu@bisLHrQEX7KKxH90zTn0GZ%aVYy59Sh5 zr-*R(k4tZA^%yaBxGZsUhfC`c;HV_}fdg=KVGfbrRL+Vy<|t*-S_~VnA;SzX(3PXS z64`nrVQ!!e3xc0vjp4)Liccr*D+$X0M#y}77d0=FVu+8~l`CmbnVf0PjPEWvA6KMl znZfyd$IQjWodP?|)OESMatXvbpF#*ZSYlkcjNEMV?uD}IhFWb<7DCI*s|dFE!4%6C z;$@D}MN!H(7fXxkeX4-7i=(RsgKwl_6g~6wO|f^n`x3HM?EYf*mFl+^^ZHSo%i=Dh zQFTSxpQ$z#;Fkfsy!|D&1Jed>1gT!L)0i`ub)T7J#K{2-;TST=&cMv}oJaQ&95|o66F$h@!^XhV3#G^xyXj`21{ z<>+q0m<1IsQ6Z|vUW~~TnC$Bt5YiS^d&Wi0>2l@!fQq4&b_fOnoV=Iu0gZ!VB5x6f zW-c8Cb(fkcvG_M}R@V&(w!xJik;? zbdPv}F9%X6_lGj$v>gaJgB`RS=m>5I>i`jin7Zroaw0B#N z?ijQsO!03lePkVs6SIeHIF$_vyB+2o){##R!f}9 zj{by>nalzxyZa0V;}8}D^Uv-W*f87TH&}g&k`OxD;<>u$Lhcioq!_}Y=35o8P-SPa z-fI#nIwu>$-x7}62HkD4C!8J@_cX%UCC&~ViD^~iu#r+LyU!fU(OQ_Uy#*KYRZr=~` zoIote)RpQz4wnZSaS3^YaWI%LfsPI00?u(0B1Lfj0AT@T9gnxfH-_<5K&WcI78PPz@WqI3E zdS(pg&@4o}!Iz*h0ACXc$+W0>ez!g%pvMnyZG$c#?-Wm&Miy|LY?wh-r=aKOc}K%j zLuhI;i{X{0Vvf%9p&WRFh(=Udj)>l-Iix3z)qN#_(e?`<$iW-AzXo4#PqG&s=fMyzKn+3{iHb~En-iIfjRahy(O?W5RW{{TnK4j8Y& zz(iGZm>g&#`(x8<2R|@J<8rj*!zv`1It;+(4wKjBB{lBUkx)Fw&)dg%M>2V);Zw;p~ zjAEuRE>s=pKhBK7kd_S&K? zFl2~i4)R(5uN7*A4|-gM>+u3;UtNsSaUsc?Xdr_4ir z9SF^tS-EgyC`RQ~a|btnV%fuo!#wqm+w=pL9xa7?3`2h@$%i(;LR` zQ5^**B{3-gyrX6@E7x!m1n2vV8WrE%KcsY_w&y3dYs?6<4j|~FBck3RA$})Fg&({< zO`l-&4w+UqxR718?7)TheviopVZ2PjSt~|y2v0H3am*s%ZB7J4Qf!RegkbbL!xD@3 zN@cLx{fAA#mb;_m{l6Geu8-9l_7;m5nM@wX#(|A0aRp9bZyn=}Ji=G8xtQ!_>c>^f zp}t^ZbFD?3MgzRIzG|MF5WyQ8XcKJaJNscZs~$&%`X|JpXTcp7DR_$%OC9B0bh&Yt z>;eK70dIGRC_KJbvDzZ`6^^?=R=I@0k7d%Lc@wV?OB3d0N(J&W4T;dl;wxyye?w^( zv?d~tM?aZDg}V^?4^|lP_z7AqyuhVLm}8Gfqu5|Zt7y0)oNYThqOFO%KC)M!(1W9h zN5cxG(lHXQLofi11VeFxRkjX;c>7#xEfS4vwE&t;`F4IIz}ay&blw*aFf?;HnB`?s zoy#Ebjs0TdO`#ZQW?gE)J1{x%pCn3sWXt!#6ZZ6oH~j)Wu*?)(8%y!A30fNSFGf`6 z8{wS16|nhZ7Vo@e>C!gEZae9n4DmQ3J_1yh%j_dGZeqGV(6K_bu zCaHf?(4h)I=!=!E!-r{NzaS-P&Okwjnv_KKjY^v)IgZb09MEoXok*U$moH9W;t*rR z9i_vFEk^A^8e+5>{0|VBF?fA}(3hA+vM44Iv4EU|KS-rBeBr;z1w%N59QrFU)KFW) zC=d5!trFo}<}h;z!XXtg^b*Nb@9a_*!k?JxaQ@!7j#%h8itp@P3CD~tc&J_3S_Wmp zF^dVz=2e`q+bDm`6E$FHnKz(l?G=P)%w+?Z;p{s<+{2qNRzC4#FWMk*GP(pnfE8Vh z*;_8v5m>R`MRzc{oX1DoFa}@-p{&~kCfn{^Sc`A1Rx`vw-linM9Lmfq$Y3REsqlP6 z+fD(rQ@^=Vw5d|Pz+`3u`imq%W?!j_UqKlqXh;VVf+_15oltp~N3=3Z8}xgTJ&T5y z%ptr&4j|$VhDk$EOt~CXymCA9!pA}Of(#tXD80#a*k^cPza#pH1m@4X~ z1h0R_iag~zg#;mx@)-cUP`CT5+m@9DpNc#jQ5Z+>-z^&o5 zG%0Et1>NoDDsv7!UgyLe=PK$x!00)g!OUQh1nUuE$!K{pN`u1C;9(+C!Yt!XWkk~? zGqH6j)$d`2=pk2JRM^-vzM#-h_?EII-X^rBMGuCu@D8YS2;OF z6?+)n^BlFe{{RWrz*7&>Y;q!|?wom*0V6555pC>c3)VFSwFNId(S=GT&WulH{R2$6 z6Qof^0daVXXT;nwFV${*MsTWqqiJwM-Yk{LEmxAr>Teslu=tjv#Kq_{curzXqRBV@ zAP?Yt+szWMg=qrW+t{S%24f9Rv1add*6l!VXd#!mbQ)1cy!4@9__3lDeFe zXNI3X(WS~Ow0q8KUm<8t+{%HL9vqUUG+1j;-a9!MLFeokohgRVX?Hr!$ji=*CG+6o zTzdfbYs?}WZhqbOStbnGgTaXU&<9x44%GQ)Gl*@c6J&KvVmcx-ba|k5(2T~a^_3Bh zi=&`t5CUaJf9~UXujUZkf4$Y71!7=n^h@3$snOxlS80hcE7a5?Ha!_+ z6!CEMc8l2ImFm3+YHtS+(26hcLc#M1OpD><_=@9-ePkyw#*_$Pd%+uI!*Za_<#kzs zoj9CBYnY5;N0Yp?GT9FQ0O~alBhf#niI}lQ$#ACEk%vp3Xs*G<-^`RVd2KRo992V`%>Ij8fN2V?<&WoAM zUV9S@?W?bLDgOY=CBR72EA8UBN!I4Z$)YLT5l7ZsWZ4~)qa2IO`aG`@5U*pHX?F&q zpNK(@AQD+mRyOc=T)Pqv3RrY~t|7eID?C6F&FnD|jN5b>g<;{0;tn-#J)<6C7;3N8 z9cIb$VXZHHIgF{Z7;RcH!6CXfDhgj&D$c)YkL}Xyj2J#-5U8X`hgJpBuZg&uGoig z#u3S0LnI6g0rMU7I%?pYPl!2)Ly;IVLG=PV8;mmzt}^kUl{thNV%lW9a}b^VWL%ZC zO zs^6&*1+LLi<3nC@_v}m5?1EqZh|5s!IPoo}>p@jsA&Vso)l+l7;D6ra!Z&RnU6S+| zMFyujDOBH_7LH_0IY0gApodF zhT1wi=)-dU4(gQRMWv}fcgG5A;|{{ZIKsAlkX7lv4wSzS#j zZL}7|qtvg`S^a#?yj34o6$jQq%)Dqr<~T4W=&=~Tm&_;$xlhb>H=jAoeP*N?V^Lbw zaYm|{otYvjvN~}GL5;>c#gZk44$_(wx!#kd#*WjNOLn*!l(Nq8_0%4W zL9WI5{ka4T1$<8XFR_+OqF3bGLtlv0y&JjCfnb4fFb^(Ry_8(cTnNh<%yTP52(@7g zvs>_LIJN<2hQP`}7i(b@TrN|cm4 zk@b*?i-UqxbW4Khr9=k+xj;t0X1YT8g-Mz=eX5&9k)6F4;tVD#WyBDSAn}%;GNxz5 z;&V8g=S76L4d8?rP9r&$FEu*B#0bYhmUiJ+1wOseoBYyv{kdJTr_ZoAo(_to?WIGc zsBh?~UFZ_KUB43Wa*tf?74BG(gknbSUitxY%`x=dzVPH3k9FrnFya}=q}rqR%heMfs@*>17+Pxl!wq>PL~t-M9H6E>=rT%!l#B30j4{{Sw% zl3~dQMUC0gI1v&kBv>wLRw87H>8X~f(yqLGqagM5kltPxONtJGiY$`Aw8SXeN8|lL z2aG;_X1vRTRWS|0OPsxyM59?_@_nJDiLJt_RK|{1W@2R!IwfXyd5aoxuJ1ra0mdkP zvM&4|+k#*Hdq>uD9io-?mOk*Q&V;FYVmDJ26pWmePdGr|13t!(%r~4$qB^yha|X5g zHI`!|AB;@QcOA5Sl=Frm)v&^#Z5?KA=va>KtW3u;0F$#N}F+FC6hBF4S zP{x525I*v`U(#M#SbahYYudO>RRB&|!!I}b#6m1H35Z8bPKQa7btjV~7t`zKW+LG# zaSfq_S<%onX6a^n$i;|hAGYH%y!KC+`s@dJY*AOQF>PV!HkYFtoJY)837fB`aa0;@ zrZ)8ET;@FK#)>ZT>V;VPzlp!DD?7(+IQB)etUqKSVTQ%(xpbEen5GRItus+?SJF!o z*j#iM{{V<0R^LsG$8~~X4Ya||b$NP69fLv8&?ElIFHjkwTH^jNm=n20E#IeV$G=D^cabCx3_=wR+(-_AQKzFEX9-D+;55)8F z861Q7eU5b?#8ftfbjn`wI~AF18>f+62GBmQY1>6iaJY|zzc(3Ev+~)No#PF)I&O-q z!qehaO0YgULCklYZ=t+y;${$>K+Fgoo#9$BMml>I4v7FsvJdUMnz^`)?)E%WGZUe- zW4z}^FsQ~qUPuDCtaS}Q>r6M$-+0DWA3-I``PlojiYz}pWu)JC5rp0Br5?n?4VUe@ z5K!E3-s1BXBX=`b)}edSYi^dU#tTo%B5R1ZR3yzE55=`C$vX=6opW$GQA#VgDaH3)=%M)6fJkimyd z6xlgor_)8CJ(j+6W;33$6*-2|<06DNgL1M;sJPlvVO@MOudIz> zz{|Xw%vsF8WYOE@ zDqTB*-SU8;5<>)DBFt9hl+K8S#&wK+ArhwxTquGdAT_X|0 z^Iz0L52Q9U2?B9Bj)HQfA51`?P_}%<2GJu>eFKQ>#E8mwpf@TuZ60Mb^y=rWEmNLG$v_EKrcwM{4a+c|j zWLSq){{Uez9XK!@iD(_;Shp4{id5#K&L%yYw|P=+dEL7#Pp0M&R%1G7E8@=wG0}N{ zNEj7(jt8TNAOIkcij+l^(QO8LE+&e;V8kSGcf7C)dT|vLF@BJS#;JgwQ{?NV4Wj3x z1$s#h#?K_T+kQ?+DfN<}iCDNWgf!sC?3uGMOXCJN6>5*NLurh8B(ix|;L#Wl47!|5 z5Sg54k>;)*7GH~p!#<`a?K2&xItUtWVH=PoLF?TE+7IoyR_y(7J%F+13r7|#R-NZM z9cK}G9aj}vEp`_uETwWr-h|sncmC!Y*hG+JN}DpE?$rZ=mEqT zgla6!>Gg$NCMr?!rXr-p1^G;J zsY~l{-Vuh->#KCdgBlUpX4>c0M)^#CZB58oE6MebtXcB`cWKQ+p|ms%MsuUQt)sj$ z%f+V)f+lv;c}tjPZgS3L&S&NjE}TAux#4Q~xu2Nx7CS}ud#)oa3aWN8{AS@8#T>gt z-WV!z8OE3C5H3g;tPjT?k!;R*CLyH+ueru!%cW!VJq}#3^&|73MTA(usdb{u-f^9} z2?H{TQG3Mm@usK@G>#Etxg9}yoj*uI6I8=v<#{wEIG}ouU@waaly24WA6cg`a|ljh zh{%I1qM_WlDs%CUNbl>tP3Fn6DOt<+iEma1&-x>F!OUvmv>g&Lmn{ymOxk@T(bAx zEIEl7#%EUp&HGU(MRHDh={d)Fc$>YO!*8Szgl~6zTQ3XQ(XuxkMUBQgMITxls|e>| z^KUGEn-~UosOt4#Jjw}&syIw5re#-V%!wHC^ftVM#1r6 zgVk`ureO$92t_HY%v6tv9XmcuGNI9rvP^_oO0hOR#A3DhhX``Y-&YGG5^k5P<0>Z` z^Bj>S<5pf~>0D@GhC;zUeS=%HC(HY4EIZ55&<|r}^BM(Jk!wy zNYEL^^4@O8F6+cU>>^2G;Ijb%XQQOffSiBWzvf2$@T~ZM0nqg;sZ# zy%ukB^Ss6|BoHbT+7rxU9YjbQCBQll@%l`QX9?z0+n)mlnTvQL+tM>M>BUXo*fxZ? zFflf0OF*Nij?nqJ4Z!mz%^we7>|$LjeOHT zcsYrqQ=JHAO|U-${mh^N1pGyuN?!0LQ`tF{)8-w746WlW?-pflD6<%ZIgIB=(h*10 z#NYta#vYrRg5+#Pfiew!ffcKuKKXz$zzTK0sYep~sMuD_uF)6X^_69vV=Jt=c`jUQ zcsSl$nRw71lVanSVPUkY+9F`l94vm_=LApaQE@6EoG{1SG&iFxsFeCwn1`H+lc-15 zR2M^uoIj0BDqq8Rshc(W(+i_>A7F&6MOH%>QT)Qhp=;%EQu8r8LupWq=2UHud3nq) z>xjeXp`)12BP%lTH;iO=fmWz&N7kvkz&qUs_S75t#IMzTg3ZAZTISt3B4a_miKC;m z89f{Jgg~a8^~`yE#vAJT21v9D3f--1_=eH3v{+-jClU54NFVMO30^%TCQ;69(2?3+ z@!QxNLeH}w#795+{Wd@h;o>aB8<0WR0#|)U-^Q36d6g4262j7+lHa_*bkxy{E;SZC z(yoh?(Yi1Uq_Duma9WHfGvY1fHc9RC0bW7pqk`vEE$mbk@Eca#ykB7pCsaGT7fN}RD~4HJ_3 zLtWg8Rd5m;t%ocpdkh2@RmvacUf&qZuawBlMS2pvoVKLtd!?{rJN-dafEM%*a9z zWkVMj9<8UA2gB_%baZ^i2ZWST$oqAb4)3#}zLgv!Q74R`Z%gS4w_n8uHMzue4P6Jk zEser&eS67O%gFt;#d<@3tc{1n4FSA&h1Qouff~iP zoAYyM#w<`JJ`ZJU3=~RrrTJM~YQ$1zE*_+dEq{qoC*Pr>7-nDphA7F%8mtCW)cCrK! z!xz(;@-%E_(7g({M}0D8_}lPJ?`iag(2Pt@B4%)#BO8Nhm8V&`=hwO(Ud4Xe_=n{; zdd=ksmMq>IUX`5<^uw`Yg+N|ETo z7k5sLM4uu)f*&FAt{GR8(r80?ImV5Kg*HV&?=e}+1S4d=N`{xuv`x`em8H3Tr;;bs zocfI7V32JYjLLYaA4foDGO-P!W;;!hxbF#N^3~1nKE2VdRptG)R(gZ)>^DMqvn=zl zgT92sOiWgd#h~}EWpBi+hVfot zurTutSJoqa1(@?NB8$9Ovh=v=nbHu19Knc#2#Ewg5blD(m>3j2ZTxoYc~=e(P>-n4a~n-2gsMu!r!NCh2)4KA!9m~FdN=P8@<&C*j~6#z^kO$~ zWkM!k#-hyPF}Ji<9zlM(H%Fx4giiWwjA0E3Ysu@~D7jm|ZCZEH7JYk&M0(@At4~?X zIhUO(5~X4?qKwhvZ-}&;6#bck%tM_qt5b9Zo-O(p7GEOO_nARvu3+fs`ib%}^^^c? ze`&ea*}i`bOik0Yd5v7_qv$2w0rrl!mizit2%gd3ns+!tK;nnf^fwH056r+yhH=te z!ljktY{iJ+#HXS$bQnH^eKRf35QzvdV$5PJN@B;%6cDVS*2SL@{SL>N-T78NA*6I; zqYhkg1wNMSA6TFCbdRVQO1%@b7;Oy(8#UE^=2SRU<1&vteU4!;6JZdbTS!}bA4Ere z4k4vSGO-q87_`(xAR)YSrp*RS!Y1)1$Y>Z(Wh$+~U$&;3%FNGQHpEXud(-luxaQxOvUt$SW}jr<8#v8AtZ7$(eWvNs zZ=1aVt>DL2y(IVt+9I*z(lar~rN?4N;mG?kV) zF$Zazi7^L95R9!AZs@^Yvvj=w0BvTIsr*gey{JTDp0htR7%<*(Gg@=#cYxu&p320y z@D&mgwB^&C=Nb+}kHrg)F{tgmW$?w$huGdvn-4jFAGR)7rtMA9)blS+zMat5n)=Vi zdP8>r(R1}5LSA~W&>3Ybwj4o!ly1y)Uez#W9Z*`{{VTscJ>nl7NNF;HJkS-^AD*i^P}rD zA-u|wG)9q`p5L6C=5!|s#A5E>5aUCWg?Vx|;YAgK)H{>Nl#UF@lmIAi^ zXXYI&#M?If#%|E@7|tSQEk%o=F&z))5ZVJ2A+#HGfgaQHMWArXdx82YJfy1z^v$0! z3kGKIORM;t2RGIqSvZ3R6{~bpZND;)3L5ew>m@@+jBU_+RG=!W_-6Jua`Q6{7{yEr zMV%p{@_iw^H;f^u6&lowo(5XpvQxl+ZFv6x&7;4sVimV)b3XbIAq;86sZ)1Hc!H_v zTrN6{Hh~!H#N8`ohjS^%NqhF0ME174!SY3X%RIo)&BS@pdBGi@F?1szPS=@#jrk|u zdI_+uv2$^trSy{ekI)AmmKt+NUY0=wmVeAMWAC{B51}sR0u*4;$w(CV8V1m zX|`DCjxTs@`@^Se$Mj0P30HkS(_&SKT)jVvAEyV{Fc(GQ5R|#GGB2CjJS;IkQ6FU0 zRoJ!j1sD9G4x*<>W-_2cIG{rR0E&i;ATg;@ut%6)fq3y=#5aovZ|$pIf7WjG>_Vxq zl=+~;Xh&$U%Z$wDM|f%Ai6ON$iAY0Tu3g3(#ltG98_i@HohIo@xy*fyuQ75&Tbq&E zo2746+>W9<`nJ5xzu?I}MSBlMI0uF&2rJWc!he-z_D`gqW0 zH9F`SP}8v$2=LFB2lXD&4HtD73zu7}eTlzY&_&T{eAEm0gyt+Etn5x$?=WSX4`UG7 zcm1^qbD!(%FcQ&}yOpQB<{LsgOXhHn((Wm7a~;V=ciMZ#DeV#)L66Mq9rWuB;%Vy_ zGhR1bEDy~odq(z*eT}a(CFv1P_F}xv#CeR|L6}E&GpwS$TYg|#zu_m`dRal0cZg07 zvKf58K)#_a@gnFGa0>1OU}&0&rTTvazr_dAX|y5MQyLWuxxO1P;lBR>h@U+XPVZu+LQ$cP zUOzEm{{VO;?a^Vh7%`hd03LbiEs)3`LL0+*yHkmpK~tAQh{c1n-}Ua7pY5QTc>e&2 zyVt&8A!crFd$5NjG%BMuB}aJg9pyQ?y!2+~D;q?IpxdLi&@&lFU%>#iT3!C9+-P$W zKE~IXm)XAXh~6^7fbTaGd!d`Tn25@Zta`Sk-W~q{27TwCfNQKudug9p0ZYr@xUxVq zlYUHSc>W?bome|T5N}f5An63C&SFZ|qG{F~Xeg}%K?4;7FAy+F3wXZO&kMl5IW6XDkV=vdb8|tI)7Dn#u z%}1CEc+k0y`_5wH+1yO){$$_y^slrQZ>E$b!23#rqWuj*_ato*h5cb~d%j>2&GP_} zr`A;%Gz3WVDmy@xDpn;%2WSu>mQzOr-3d2-C2bIe1S`O^yG!p&MPca?afHf`y! zdWYU2>D%oeQ8t7Sc7!z&06q=iV(8zYE?)0#x?uOH42L3p zAj`~YVTe@Op|o7a^eVx*_>1d$$%m8sYh?N=ZuRb&Bc;Pb-d2WS&Lr)6~$9OUJ4dtBxzJes0!aopY`G+KV(SsRXV=i$R z`czhqzhOTK>06KVA9yoB;%;EIfGXeH%)`@t7W+qRWMAG8-g7Eepq^uyNKm5RNbULd2jMT*_SAPK{U-O;ZwyPg zR22K@Zxb*YgDPGj9w+dPF~lz$9q|&GwXrj&IyTDh3%J5scE|GvjgOunFZa;mG4@wt z{{U#s>tBW=b+U{XHneYzM8=0P(cjdqrtE=X*8Owp7!GO*^GklkceBfp@GbsOqbS@4gjm55a03ngo^Zal)Hh#iuBSg*++WSnLP z#5|flo5fZxzZJ)QcTOP~!IEDGyMDw?T}Ar33Cv~3E({~P2GRHeZ~FuQRSWt3wX4*A zDZTZQ&aj6#H4WlJXlNLmXn4@f8~qW6bi(4BF)MPRyfG7b=Tcfa24J^eoViBzo@VNf z%yy5mK|2L}%P8cJ4Dh!GJI5z@Va$5YJHazJ(c98Tbh6y|%-Vm^SEZvBEn9~@vhkhY&)gO|aTK%xSC# z6TFE07agykY;(LeJ>^iUs(o=jDEbPEahS#+&Vd<;xPYl;&zOpEVF}EAB0Ir{m>tkX zm!Db5DJ|`Pwy4d?eW9Z6`wQ_BUe;#k5Zspy*t0p%n+e2RN)HqCZe-pD`f0h+T&*4B za3iMgAA#>VN-EiLJYp4zQ9YC^g9+CM#zcD+;xRFC(@>3Oa~&B`)ZSun3H6Gehu2@H zbW!g&@Q7!3NGtRnVKt>(Nw+?aO%c3(-ncKiJ;mW&Ep#WVjE4neBuHJ z3+O`h8D&KD3Cttrl$G%v{bI|x6=`%=?XBazePtuR*rTvrrFZVam|-IZU=Wjt^^Qm} z+CMlx(WRlg?Y!L*5}~|I;xU}XL(J&KcjS`d^@k5=p{8)??dxuo89>%PN~xHBCqFRR zj?n3YF_`TW*UU%vgbqmfFjAg^9j=?ogwU=qPnyI;jgk5+=g{*mBc%}scoEy;U6dbK z>~<L=#18<2~wH@5oy>#!l!tPLH__T!xdyb*~Aw59c4WK0P<8U%bV5OBHQk@ zjTjf8-cYJ72Fjlh*gMJ$xbyUkWqaiL&O2VB8%#i4<_5G0lL_>sp*zzvufcyP?d%ZV z7Xe9Ez1cw%D)?Nr^;z0+9w3BvoW^A-biA*frJXGXj&w}v#4>4rk{I1H9sP9@p z{k607GO^$6F-x1owRJB;4YWJQP=s!$lr{B?AS@nfn7Ph`yN)M1JI&B7C0sq}hi18H z;u>DDyNq-;(~nICU2iGhA|B&8lpCmIK5-C3u=%L%>l{X2V=gw$rgrzyf61Q{-vG1kxyi+4EZ>ORvuZQyA@g89_4fvZujZ>ivPa-~=&$UM@=5ZaNIgGi+ zj?j%4YVo(Mm>omyEKcw3tDzk_FWNr3m%ORtNti{T%v8+C(bE&=Sb!)zm1ojp3|x1E z4fJBl-Z5qz{z|;Dn9pIp@YDH*qJ&uJ==uhx0kpdcxC%S}08&W3#Nkv0o>dvzmZ{^vtd1)e`H7 zhEp8OG?P4%)nN9SZgB&g;EQ8CD}NYh?+QKR|Yp_N{#DQd3P4Z@pGp0of^4Y!W+dD z$bJbLr8$pMp#2H&dB$L* zMF(JCi05wjpVC^(9nP@toN`JMndKmHb(RU2{vj@}JTo-ojvy!zg&8ZX6vv#*G!-su z?*Q&!#3~xcL-~zaAYpC9&1XDK@|cz>U8<(>^8)z!ikGjL=}N>wI{|^^9Qed zblrxZnM3HnxEd=FVsS1~JWGaVYR&WlV6~DQ!Up;pzqwKRaej;i%CH@BM(xf>v7@6n z^$1;rTTrO*V>apdfSZs*6(udiimRQWw9MnusUT-?&(9H%@3Z9e)fvx{BEgD{*>#|%D;(aW?=f}6JMV51`}*VIN!{859Ty*_bv+R?xJy5^p;l32<;@g%nSH$ z9{ckuK28Xmo@PfK<5$dKf#dTFJb$Qc!}Aa)XRKe)zJ@NQ76@MqWeVj6C4Dw7&Rz_$ zPhCT?f-j)dtvAO0AjAp02rVGDt%wQ|QJnlP$F@RBnbuSXG!D5oo3{k|6&r@1Go2c^ z@fKz$MGLmkEqD6M4H?9!>P7??vfrCCdCXvr z@r+&VXNDJ6cInz2&Sx>vNk+8~Fu?{x{{Ri6A?kssWn6UQ*I3I@yX49~^}Zi6CF6ux#&{!2-c%HtIUsL%s9L2z zNb1*Vf2IM?_z3s7HQ-Bu2^_M=4*UAKj)$Lj+7nj7qv|FhwEUsGAqX=D5xkM!H^B1Z zb`po#<{OZho^l~Ka3{t~_Lj#f`stnLFlHtr7zpf@O8m!E_DDl*NV;*UU?u}QW-DHO zVh;27zis>keW9dx^^ipz1gY^0xS5LMsozyfnLR2t1de*7$ej3;=;+RLRfJ#$!u~`S zl~uzi2S}u@NI{s+zK2&x&UyK3406EY9PG%)0vO#BGPjQLh}`B>PgTo_ROMfif|T^; zPS6Q-sEl`t3AqMHKc}-94xgsKDGI^QL^79Fi_kb?sA)F)Wpl_<&H}hs!ga-ol0AXG1!M$iiRTQqZ~(V4X0-W z3fm4lIVT5YIn#*6%yn6eBXi|5_F*VAYS&ZvfH3MsrGl7<==z8*vGSZsSz!4vnO?Bj zyf`{Bl^TpUS}7k=glwDq$Hd)vBk}1}%lqPRO%Rf+Uk?mV6-<9l^`Q2Jf)7Rn41GyS z@D`#A1M?2F2Mh|{WZcp-1LkwT2sL{5imf^gqPO~!0q0$bU|cU^0<|w3vn%N#vBvoS z0JK7`N6oZ-B*X}B3#sCGLqQC@A^DbT`bX zP3CmwcNGaaDw_>U2dksBCozn4G=;DporLweDM(*KZN?8r#z4@Z&6V17!z_J+?SB+ z6$kY~&6yuFY%iE>S}D$NN-O=*P1n{b=k;O@))C?lAd3>p!75T%zL987fJ=LSV=h>} zvJ^##8G{Lt4X|5M_V7Sm+jrtV(1qdJKF1J-@Z4C^JIPr$;EcBa09Bl4eHpS5(B4xx z5t{`Em`hNV$hPTzyUn!w}B{UN%0l7)oy^N1sa!rAFH`4K$#>98ZhZ?Fx;w`~hTq%| z89y;LslB6EiK^+NA7FC<_djV3spEnWTj~R;1?aDTo}eag)**p@+CxU z_-`LO$I`PGz8yk9S zLrj=64WNe8cy@sudW^6zjwoNUB0ZaZq6=nL2s02%8>7nySA8l3>8BFrVay|}Fdb$G z68ba{%rxB%4GVFsD{7~_MSJ?mf^&bt`*Y@Eeb7_uHH&9HkdC8`ag!axn(M_#fO>60^5Q(lo>Yls54aF;%3ohXZ974_N3w z`o^Y%nSQF8Jr1!LwQYz5r#F_V>cusx`UdQyjQ~42`I$XLBV56RI>UGnA&fDTrc}1a zSzc`S<6+us zWc)>+Sd_GY#bS1{UmlwPfoWJ`zO1fHeQZJ4h6a}^V{MWlf~_BjpZdE?fmuQz*gUgtE5o4?QOvZr zimJtLr4SQ3;85&t{KHNL0y@^D37A2JhE`z$98avWm#Yr;7sr+#6w|>UXdy_aa+5wf zJ>??UFUyzn7cn!SOUxssu3@}fVKL@T^RA3^;yUJ`F4&?`Pan*Eeee*e>|pr)xyqX) zH9HXbkFMF?61!MJDDNJ#iNxYN#LRb`$})042#B!9`If?CMsCvO+q@k7RnK)8G-AdK zcw!7To4t{nK&fsxKUu#V{{Vt!by)ELi_FRr48{~eIg5u9rtvcv`WO>(Tb&2sATr3) z>ZcNl<|@1}iyiNtQOH@sY?1!})V#on)Czh+9wZKX>2Xvw7lYTGC|2|z+(c9fqoCM} z)MyZk6Egw^AqHXMRI6!dUIS=F99#Wwv~qIC*$733iD0d>3fWCPvtmB(tb0!Is7jnh zFkHeRVD^S@c0Y(pjAceTRCaZOr!y7^E(LyM1)A-9%Yry#hGa$?$58Yr*#Xz7aZ0Z2*?5S7Oh=i1kgXFRnrJ`sL|o=p*H$m4r!uBK znhBHhDg>w?!4aoc5DLXa+6&+|K4T^45%rNF0t~S_%K`0L{{RreIS=o>b<}(|LdjMtM5EK=P zV8+AH5M?Jbl(>*lmrIMkr{XGeSre>pM@||rW4ogOwLr^b6DY~CH{UQEY{7#u-fH!(I&?xAW2(YBaRW2Jv-FnLA6gO14<5ijZs@0EW#HJp zsQS=O7>3f$&SmH$z08Zyaigj+io4kOIAK#oEwg6e&bOlye8tRY#bC93TM52^Uw-je z%uXc?SdX(w6q4vXtNM$RAScXs56#(#X?Nj?n273s%?%&TF|y=5D3 za4dgsiB{mg11sw+T?jzZqD(<-CFMI8FuNo^cXT~ReHbAx#SfYDZ(@^ySHy0-zqe|V zaYQTQBzN_^#KIui^AMfkysmW|RCFf#43UIqY#$6ui$yxXn7^2*aOiI?aRww}L8uDJ zVCpz#N2m`hsEGB2!xfTLbY=R)@`ZhzLzp>EuOuT5AGE2co0@ZZ7~FTA02IYRYwcbbh~^sJnI%>(@MJ_{2#qk1pssK9i`;g}iiIX4319gLv_UpK)+78j z19(cxa7^zFOO9hJ^(v?c2EzSkevp>zAAa$DbukT~PST)C&~q2DGO2RutyMEqbt_no5EU)hy9s`jMl%z_a%CmnU0V$hUp;(Nxc3plPjM80Ff0Uf8?44 zR#I_Wb7myi1Nn;zfrwBUYACikx73ORI(8rWxFTJf-hT&ZuheN%m`j0oh!7zR1~xjS zBOGlnfPJC61@`(o9?fGw#NNPV`bM*{v(vn?4(@kgMRCf`CUhYP?-(GeH@*bj^tgm~ zl^E>_dKxJmV3d0Zx%J$T9Qs@5_Vn-U;rW=GXV_~la||lYq0>dnnVXTBfyKl|bE4EV z)aMQvM~d#(eBvC=b*Qq4RmYgK8rLTKb05^O>w(2+@O!8(%~lE3 z+vylu+#B;4LRCcKBoUPws2ObOs(OZ?bgGVOyJr0)tL`O+!>_K!WmK6tn{^5)!&2tQ z9FEMtri{D~h{$rIk6q*Sz_2}m%I!9wx7--r>(XyH;3pt?j^^!%x%C3=EfTjKdppWQ zRMj5Rb!+RW6EmBbl?-$yE1axVKS@s4L4Bs=7<*6m9Tz^uOlinvt|*1(8CD5X>nfHO zu7?k#P<58d#5RGWzKF1>F;m|N0JaMG<{QA@OO+XNp*Vq=SmB9_ZGOWA%R6Af@%ws$ zoHB{*Huo^ce_a8VCP7sZ&K(9Y+70MKm|+=wcG){I#^@&NxcwrqWMW#wS6YEkRMI1G zg~f}5VEV(cHd~ew?3=IKIV8#*)a5rmBM(B(_Z+&NIF><(#NIJ66EQI=p;EPdHnVO> zP4aag=!!$WHcqE+TT7uc6y<9Y$=NdlA}25g4M!A0HqJLZk)RPjP#P%f>{K$PVF3!W zn-S^w#_yP^R@mhP1EV&E9L8^~TW{16T86ja=yzcdq2ccD6(@JrlBH-)U}r!|C6i)U z4j_*+8DO$>Kiqm)#5x3hq8rQY5K)03GU+XJ%>834mmViE#O7vHCB_knF4#*p_kURK zxnVfbn@2-wQMq%17IRB!VEV`?+a-Br!`1!0A|I?5jnHNFkgSWoCSci@H!(X-b7nY- zj2ny!QdBW+Wy^3bc5NEDj#0x6T>^8X%y2%s4WCE^*dLx_sB;pQEXo+@`y1v>2V{s~ z;gpxA11EWvJUyW2Nz-KNc9rVdB3YK@ zn1M^&@f^veft|RAfqw($4%C}uBb15yJ5;@I*HW;I#xAoESpNWpL~Bo75!#3yfqF%J zbcnHiK@=-=`w#904oAAdV!olyp)WJGfSf{92-2*_ZCQU3_%OCx*!nLBMOF5$ZiH?o z2}p&sw*)I1o}bvu7UjYLDiE2Am~u)*kl%hLO_liK1S05f7BcPBGb@}75*-8S7e28U z2)lfK-|sX9e8qcluVIF2m?W|@-f=cohgiZ1T8^eaEjE2Xl(|c4=i)eo$%({uO(~5_ z%mnb{>)ISyTR%hMS&mtWGB(V5K#at#Yg0-Xm$Gl}aq|LJl%df#A|NW`ClSsg&XPtZ zW3;G}rD8E9X!<(HcOGDMzb#!J(V%w|Wa@VI8<_Wvp!JKH<0M3LE;*B_WMH`;ndS`( ziB}#(5+Z)4mINJHOZCb0i;2-c5!#;ION-D#Gc+kxbC~a}6{1|XE90@%hr#4a1Zs=w z+7hQQrdA+8hloIFVkJjGh6AH5xBVhS({?_hI&laaLPvFA%PSZL%h=`mKx1Th2(v3Q zeWA16KmJ$xcKX23y8=D{t^UP0M z0t2o>BAsV9FEEENo0A4}IjPq~GjX`;7M?4>_+ybbWopJ6I#iH|-GS9?_})7M5-@DT zo0vyGGnv;%SJ3%VGTsV-$6MwV{rHJWIH+lZp0HknDkP~_5)g?qA67k)+~Jko?QT4s zqr;4&*ne?7XgVP<(=tmjO|BwKhPyKH2^DxD;8(`Pn-i(DVv#gcDfMGqdIDoxMZp#o zb~>C8C7+wjU~{W5?L*a<5TKciNVkA2M)MJI6@6&5Lbs{UF337S*=%=v$BK*TttwWR zJ|IDvP#77Evbc==&4Aie3&J=b^)D=cC}V!J&=Uk?4q)C}cEmJvQjUSU=V@}dkb*GL z=TUffmx#q|d`9edhSA&5$3TlQz#Fj)VRI~?K3I##m;= zHq#msv!X=ZqGl~E=1!{uKGF*u*>S>RUW|1}EM4f?9g)IEW?IX}Re2)~PEqp$l*Y!} zF$(@Y2S?L$E(dPi=Hs)==`jTq+0G?7iE$v8D1v-OIw!I&sH_Fv{PZwOy!q@ARVS|D zJHx^}!axHr(-URM0OZk&6kt?-K$y}Va5Bag@r<}=I-&eWP*uJr;OB&*RIgYt=`(6y zNmCXu+t?E?IN;m@l{?JB6QIEra;}Qtj+USe6tTwft|I#icIyompY2|RCw3s9Z1#*5 zW%_GE6){q{l@{0<1|gUbOB+ax0$c%?#4IUY7gx+sxnt}a0>DD$jmHdNVYW8|rZW6a z0RI31GIg}y(`rS|%^dK!HCql|uP`)pBE}{mykVAY%nZj4%46#jZe+iY+!HOp{$r$N zC$PYeGN;_jLku>Vmk7*Myuom|-AoCpsoHONhm=g0k)>Q)kBV z#$BuL9`Vc{c*kZs*R4T_}#C{_Q+9oHB5<0$uBodMh z!fm&+&`T8#Q-3{VQ`i`oxK%Mz5O77zEwn_?M$ABb6E^td25;-W6PzzfXERR%r#*S3 zRcw4>`;Uak;||C-A3_Nzd?1#JyJsEvZdb^=66(W+K#$aLxnKnJ6%)TW#a%XGoGi|* zrWW*7<}%)HBKsi3uETb2K#x9dtf{%UH)E@PRSgM=P%|qKLR76qiCNH<9>hkF$o?TK zVS&&h`(cLLqDxKOUM?td^b zWN>3T9!M>mn?Vq*r3`eYwK$H9eHE*zUB;?eVaCIf4qYY#;6KdgBO+u@qY?Hl3z?nF zAX6Gp7#`rkOH*hrZVF~xxo|F64$tcV{>Xo_pZ%!5{{T`cy86XD%a@>;vk^PuQCPXf zEtTv68QqGwiu*25T(P;?2ac?eDIdQm9uG#$hi7sNRjChVfLX9O9seB(zri`FL*Nq)^h zMK5Nzr(YVm6}sYtR}Pgf^9MATpp!A;6TBZp`9aaRN{+Ykv_|Rb!KJ z<&IL-j{e1HK*TDM49%uF3^JY`!TrmZMjDgESP&j5Wz?c09dNr50hMk9B}N-YW-_A= zU>2Aq6N|)sX4!`C{)g@zz1@M?k4=lmk6}gWEpX#89pROyX;v_s;#;F|X{gPSm!BVl z{wh>vo$&%P_=77~6J|7;9_mn!h#U(UHh2C+qLT5PR7<7~CMyQ7uFN_ zH1#_%gm;L;D2Wpg5hh5NDh0g44L;FLmqVXYSLT9>uCr9d-u&Qwv$y2I|Ot2#3)QfqS?fA~IVGojgIfGlF z&p*&cL}|}|G1k`N`xOHdc~C9~%wRDB3Lrp&Y&^!hPZ5B%T*qqm&lf%jn=Q8&**1m%%KC#sjzR8l+>UGC!sdSrXz>R%Kvwg_)db%t_)f$tuW__R&nb56mi% zz{@IZkb^%6`GuBvmXd50whUTl(?CULUxtRNm0A@Y>VPKXm0C1ViUFd4tx(1kmc;| zJ{Y0N?^`VZ<{f<=>^iZ2W7lQ+%X|A_2iWC`DTp|z{zyxU>7EWF#cwU@t!f$r0hPoO zu>$2VAZQ}K2<0_g`H;yU96Fdj0 zxA3qsh?+aZXIWJPbF3XDM@QIMLN{^X*xMW4jLEj_eq}wxbk>TPyusaN|2{!}(01vkvS#F;We071{leYLd zmNP?Z!0<)wV;Ac^1gJ_v60R7V!3vV)6Qst(GMXa;ah3bbx?N{iRxW+fWyfeYV42YK z(l~9`TYspHg|2ioH0EB0@LXI>4SK8-cW(9F}|UOha)0m}n~g_&O^+TX5NdcgVy|m;I**v3jL%$mTg!la79o zM(3u#QL++{3iKAkquD=%+5$iHVZvTH@}EK?*-?dGU^EC~8dNrc0}v%j#G+9yVxo*e zte5;i0lXi`_L&Q(neE~#S3}ZiQHFuNreaiu1o08Vn(TA%Atu3H-Cvlur!X-pbLcmN za|&GBL(g(K$gHWhp%HMzVI3J}!OOE?R$aNwb z8o0wvrYX&O35_36#XzfXWME_KR7IFYR6NTL(X>qjoWz_{6hw(CrRKD$PqZvD<|}FS zjtw93DitgpKEO}KN9YJL$<`_K-Y}e;Y{70P>C>?)0Ixu{A)lY0p#`yhU;gtL9*uon z2!Um%qrda85Wzd-^ zM~LkE7BSZDpHufArdN}xH<_61`wKIm(@~pPd7Mp>=M#e}v#K~^W;YVz@wbzy4;}?% zUr!rH@w&*Brp}caQjFYogj$gtGa9Zi_+AWntgJjfA{93Ya>cxHtD)*C41;VQJCW~- ztKHm!f%T1nhI2&X1`J5u#xWR|6A?2QN{JKNwps{rh;x{sLB- z9TED=s?S$1-aD1~ozH1nfhy(A;|<_uQ33=Z4F#`g?EwrJ%tuys9IrwfsFeo^!QMP; zRz9=R3pYo+M--c5kGy7P`byc`z~((xtYyL))@Lz+a=fD=yA=qbHmnQGDHXez)zRq) z>$@DhS#XGY5f=T5!OF(&nEl5ZWrgzjLr<_fYM5r%iz8x>5SuXCVxrA4vOJ?0k%UMZ zZ&B3b`H4k<4uix%CP2Y)sxUmE%6#YgKd~|U-u&IZ> zUe8F@Y;y?h23`6Ww*XhfAzVrZt)p)CvW_+jB} z3DzcS5e3>|vZWC7v7MLIJ722+O@pwmA{%1u5!ikaJcojB zUs5NOUn>~7Z?qbaB}A8r#0U$Rf~ajO0RjXv(6zoJsVZH#CQe(x-FiZ_NWrlF<2I_t z+St&8%xii=!HbBLyj2|bAg;FapxF54I>p9@T{(fN-UeaRWjDXXZZkCh0G;PBxM=9P z(IrKjvF;cpWs1TgudpS1GvG_N1`i*(DI19Xs5pY3VNra-<6zz@E;Kin9pV^7N_1tl ztA?5d5jZwwmciG?-17hgMk8~qCoq*ZoM=TfoIZy|;op~NeZ)<;NbzvNx>doLiKAM+ zv?77oD~{)Q?Cso=-2rqv@fd6oim;oN8uudsDkMme9&RQWqHhq5rHmo8f_ zTP|F`ez7Y|-h`=fg%Yimv>}Fufrv3Oz^LwBr%mD>0Na>dTV=^*f-YB3bRy>~);`It zXu7ii7VrzF8F=$7L(6yk_>EJREy8aCR5ZB2xWXKl7+yr@geiG(;t-23MiSvT(N?;` zce*nc^Xv@G-}lsC+@aeQqla<(dkUBfY&556m9}L=X^{r4E-agoSvZ>|6SJdYF#xfI z=ZAGgNsb2@v$a5r81b#arDii|_0lq(hRMDBJVKG0F!0=9Ce1-*Z0!YvMcyPGUtmKm z4`;SWO|6c%aQ5#APJuSeyrbucrJWe%h$9i#{;66hl^xiLRjU(1I@2HgS4zaVuq}cC zFKJs!An93w26P3>TTh6Na-EZYnS+4p^dUuy!P+0CtY2_50v6oMkQrtIyj&18dqx$( z{$s@W4i1EFV>$-?t@h z_>RHQKXTP00P^#_dO@FHKM z_VE_BGm#Hdrx7CN6BQol{`x}QxnKAvNl~^gzS4%q(Gi_w#iv~zE7eeHV{M|7Waz|) zeXi&DFuQ3d8$Bzm5~B=ODVx1x4(%3_qD0cO8;)S6B%?}QMFZs#Y>KiBEwc7<{K)sjOVa3 z3P-khi=W)ftcOVzoO=Yg{SPvL5~flt5PHWac&{1}-n1fbD44#Wzxduzpk3HbKd3cq z%7h(RS&urf%q%g<;UCNzk(GNffcK8^NyMwkIj3o{h{Z&Ntt;3-A~zfvrfh#(9;>`2 zCbjp+WSpF&@>lB2vC)$ zgUa=PxJKYzySjKHE$GT1dRP|2>A*tmR}YlfBBi#$(1h<9TDkN&U|g}Wjvy;-h`z#+ z!IkI`adZ!l+|-D`8R_^zPp||#FxcdX^~6r$p@wg8-lK-$oX%z#nk+)#gif)pS7BY= ztnx)PD`jlnc$F+pVXqUI?>^GBWpbDs$MfvOS&u9+&@$d%#WDpX9vGNIW4dMXiFl>!D~0%f8dkq#m`%<5FSG&~0D(pYZxW-}Is z0N6T3_UbwW37X+LFiSJFY~^?&s_~(>N&Lo2>Q3^pqX@BQIJkmtuv!}y+`eLSDPFP6 z4KGWZ##CTOXGasPAZXXjWnW+{dY{7!Z|-r`v7@sn9Bz2_Mdu5WIJY@GFjELQ(a9=m z3WJE4(A>UaSvSZ&^CfMY%b)2SMc;_Qt1539^DBKEk*K1840O1AoJIy+bpl5SV{#f< zyzIu0XceoswjqnT!DhRXv0R1Neaj#=%WlCKDOUZOmFNf>7DGT{^A;Q}arCb2O8Q3b*Ozo+oV&%) z-c;kz&Si|o5WMr-<}#S8{=Mc_;aaelF;eF+&?_2-QptKe#r2Ra>QrZVFde^h#-J0Y zet*(b`xPV#tBhx8^B9aag9f7QxQgl00x?XMM&X8S)-kOfO5y54EY|5f2zG4YiA>{G zGPGkZ1Zqaaj!%CD=yf@kT|wV}FPYO$vGan?O-g#sWf+wyfd~*nDTt6;?!{b?T8gR#=KlbQfkEtl-9}*S zo4$I-TXM_x>Sj|1SY}MX;qMXD0cDk&6|?YuB0*}!%(+s%Ig1N1nZjD|5e zifDo-b}$nYbs%DxOOEl0?%RbJA=7L124ptPpLFI0&>Z#NS$MKPNaTYNORUNq;xLT~ zDk3FllYFd3Eh(AnE3~Ru?L<-fiXNql>s(| zN|b1+$^=w^irAfBq$3eQWlkqsRVmb!dR&1GC zqM;i0I=@$Lk=xqE(495kMqYuP+n5$Ncn;oWd4?e>2ApcTm8HTVmMZ01%yh4?5UbaG zu?oum=#FAG{ND)az5Riv4Yv4*f%!BJ7-Xm!jWaby6C+1UvY?QhLEe#7U7Qab!bD52 zW8N`hWZpP~h$Ohmi5=qVOu|DunU$0HKNEhEu-WMyUQL;A5iF)ra=j0=a^s;Y9PBgb zAwe07ZUL zgmgDVsagdRpp~7aFB#k(zlV?7>arr_Svj$YBiTwNGctZU4@E(R-Bp?VGh z)BPeMrMZB>(2EGTm!(1|GD2KO5kzrueS#+I96L8XU)?wRBXDo(*4%^{_5mYFGPMPH zj_5(#Qs!)iW=32{?Kzysn02y!-w{R%@K=N$V zs)$2+Bau0Y)-aKdV#F~kyls@kk20zTB^sDYWjUF2M&)Um+Gb{E_mtEPAqEiA? ztY{d5gc;~55yS&?Ghis|9RcwjXf9;P-1SSlyU(9q}-`zuLwpU|=3e2jN>n92?GS5c>Bv(UbAqa6Dk;FF4xmk|U zQV%iGwwCeOfu6=Dzxop(p6Q-9Lm<>9{IE5oN5t(YY;p!jB8rIVja#cm}mlatV zOevS4_StcJRKoytcODo9zOj?Cl$F|W{TQRD`}UY_vn@XiuL&Gj)DyMG4F(xuGZ}8R zzKXbz5D8q$BP%kB(z`&J&|9vFRy`XNxC(yuiDB_=pg9d_@qEMTscJ9K_RzZKX%pG`OgtI!}s$lfwpA^fz#Ag>GukTgH-cou-x}Lv=@e;2N`NkemXymxWf7H`S(O1D=5Z1u z2o8%OBp5|;4(XVpINBq0RZ5iM9ISZA{Uy5nVmlF*&m;6Wix+!F-qlLdy(A4GN^1H? zFMKa=gllUXUJG;G+CD%@hY{DFvscCDOl_`|Ub*XPasci%f3K`9qC6^KA&<*D87ImC9Chf)TD9w0)T4S!n zpf5{5tF&x5=eOsHY6!jPQnM|6>Xd0ynO7d>Ff(xAt8+T}ac+OyW<$L9XHKkMEn@YA zs2aL6n6NGgu^6|2fhZn^6h+%$P~as%mpP3?4!mehiM)CY3zv@p9V_e*@cwrKK38w< zxW!x@+m4TzxTzmv;e=``0dC{GRJM$IlNgswcDZ&(csQ36P4sMWE6Ng!*!4=0E358Nf^8r%E~*dRL;op^g}-h0^gT z7v}BnyhY%7NeVX%?#K#uP7_2fw zk`V-nw74wpL~rH|<3?ng=!0k09-|6Y!@qNeBhG0_Nb`Lm#YoJW+~QrwS9r^)fRRO@ z*fhOY@hbwVJ)B_Ze8xd|u{+8jWj2oKpy3@Za~(z}wlS+i<{Bx{Ec?Xno6QCIi&#$& z{{T5#>NKNsNQxP=+wCEU*QeHwP0%tVHhXeXC$GuOf;0xuPubt3c$D|xrA zoftxlwIdT`lX0ee{{V6Lg<2MOA~p+@`bRLuxSNo4m5E-2(*2wCToz{#gjNoj5re)f z{ib-2arcxf4R&kvWkoPNgeX~Fl`W_{^!S5Y6w3_-5dpOq)D;2N35gdPAs#Q{6ljgx zpz1PimhladakMkBiL?o}_KMZj~B1hOHjWYQ3WsbpxoK6NH%pbO3gz-q?ej2 zhvIT~h3U6&>&abVgH6h<^teZD6GhWH3m3XNK^*p){)PHO#yu0~G17opn9C?-}NZ&q@Uyh^h!YS+BTlj(?9f6iNFN=<4Mei;5 zk40&n12Uk*zpT0|gBS;_5=M4s{{V;sP^Tnvn> zA=2v|v4=sjp^+V*{dx5f|nd#oV=BX;8+AgGbw6P3d!H zQZa$^D%>vf?_Dj!aC}qoAG>;zT*K3=XXY$dkxZ>Y`3yHlt*pcsGV*=YrUR#`-e$Xa z;Q?!W59SM)>dKr; zsZygEof%n|7%&Q$Z*t=pDqfe_a1Gh#LH;8C?{J9i)(nBV4qYIxWT4}jb&c1}$}o&9 z+(D)+GF0X=sBbGl5>-H$h=gk~=z3FQ{5=>^(S65J?*+ETQ8v(6>XU3~#BVT*aYY~~ z7LMZ-m~(2!&HPGQaTX79RMN$=Ayg*tcqN|E8{RQ_l^km8QTOlBY`Jpf%XOW`cNyQE z>E31f%XOD7T)4W|yn1UArD>Q-S%Wfd4EjURHNZs8>?@epglp)@`E-j`I~1xpMT*j2Z?xhfunvF<03(&72Hh z%u<}cybk4JAIz)nvbaVkur(58Ma{JP{Ryz%PLq|e8N>Qti+~0NyHUd z4^>Jtw`MfewoRnwhoGO9B=j4-N{Hiu#J)Qe(T4AJ`-y-Ehm;hh{gjyV|E zxYe1BW^#Hf9=G(#>8)`u(!S+78gZaZN6-vQVsBHRn|hA2=ni*ktNNHzb!?9gZaNaJ z_ntZt)mxW(R0tB-LR7hPeF0ob6TZMF+%OnpdNv=IybYn~XXY-K-a0biz+JFC)r*J~ zkFjGuNj>{TPaGW&-VsJqfJXkm+5>2mp2`e%YEAmzNa-9T7&(=3QW1+P8^&IAI!Csa zjgYf93$k?TAsmX?$>XUB6o+_%$9*rn<=QP9`;}QqrZkgGVzU(pMA|r;G18JJzcl zwX_9k;7_dcFPJzC2gFuhhG8qvOT_0)#Q+%3W(&*=DNr{@J|O*pv9L;poM^f%tX#W` znR+f@aNYqzA7_~RBu$Q4{{Uitc*!G46o-K6bL5n-vgk;`Vg<>?4LT%%NGxv-`WGtY zXGT{@=RhSE!N2-KNCR%&=k*Xq?VTfxPeXHf$7qw7O48+RF@W3`zjEA_oPK`A*Wy%| zQFvE-m^gfGR}@$gd+jjU5|N}8GKo>ILflVHMnCv9D@ycKtu9mz4FOaN%OJrU=@J_e zC{1Gj0EA&BTdQySiU!UN-!C!LX2i`)fh$1t8UzLkxP`h9%61?lvgkW7u{<1T6Kxkv z5n;Z!IhPKC*@t&r#^B=O-?AV#nD=ebe|amgtCSs1$H@4VKFT{F2is-*LPoB%MAK=u z(MK?1Lv|z~bvAEQsGys@rTxeauPHY>Kg=?e$yvuzJ_RGQDg>9EUa=y^;@PfqE)Zd2 znAt?F6@$+Y%z8AcydC3s-usdihB^}9QK`0O4q%9qmk}{4GWBM3sZXxGs{I-1E6{0D zu?D~pTtghg#FY`(Xl(kh^1UV^CQ1b@4YzB5)J;^cSmigDEzl)rN`${gVHQ3KLt?P* zJX{3rv9-`6lvRYO#*7^88mQ@*#`IF5pvy1j&7#!Ui+;+k2wT11Xo+3_0C|C^VK&Ia z$K^UbP$#nJjN0vi>ngC%ND)R3U=bKSVoQn4y#Dm&$K-Y(o<-f|^iR60Bl5~z6tWOj+{nhhffsDm$Q zRgONeRv0xGGP4#X;u4_eR?Kv$kdmT1kexRr*bphY$#AHZT)tEZlevsLzW!CbL$qPHh3mS10 zF(p|m(#Lsv)|Kp3t#8}nCYP;B%n4HPIe-&5_k!Z(FzW~c-l|#ROAX(F5b)!yU7)QU zU-UR5m8;c?W?`j639+vlF<~xLt_x#|0hPmOQPu)@I(B8j<=E%Utpc85S{AgpxsHw^ z>I@55?wcYsF&6!j(C;46)t1}AvHQ|B8uOLZckMT(8onP9J&@|A6J9GZ(CptEM-uTk z(`Ye?1|b=k%Zz4BlEjB&{{R&V6|o=Bq~9H8x}Bo|DT!KCQgfj=(w!%H8+nL~!e$Gj4vlRWi zk^xA7Q)ia8fG?bW5Z3PaqZyeXxa}yZlWL7vh`W#xV-QNvlB-J0Nm`~f&ZnVP4)Z!( zuSqIYuF{{jsv`&yaBW%_5xCS^s(;Kqd=JWpm(_&{jEg$Y~ zOB+j-oj8?=P&Ce!97~MOh_a;j+z~KohRE^WD$8_k_~=Ddu3%;pW|tL<7FEtAOOByI zRtcCeG6{0vIPlRayl&LQ@x-nsBi;KQ>P!v6qKEVzS`1KL|07_i!8 zL#0IG1+mK!7?CF$)Jah$Ql&(dDpaT$l`1e9l?F0EXT|_=i%m zm$?;2!(o7dHqzzGYfQz`^trsogadfNMzPQ+rS?fed1na?-mJgg;i!nm;24gRle|Qg zpJ)O(C09bOISl?|fhjb^#LO^=T;`@_YMPUI;$WWY+8qlGU5oN2VqEneMB*_#JH;0n zn}*t1HkAUj5;q9Z8-dqV!5qz@V0BZah+M5G4?71h%a8oRaz`-~l; z)p%(*>8xm7XiWv36xM;6P>X2}UJaC<5IRg{ zNUMmTr=HQLFgK3^&0FGEiE6^_h*r!OMs4YcSP)o_0quNF5~jefx-tvpe(`+H*NCQB zZE9BXrAnK6dTYjnPytz1pcddLjTQ<+9aYvKjBkHw;UemduZL0~hkt36kb5ktY)c<`N+~qG^s1LP z(NRI?;V=r76eU=SBBA{6xbSfTM^LLK{n$b2`+-sLG5@14^9Y4q*Xv9>?&( zL%D_hN@p=UmGo{>Edqo2h(#i*>R$B0nH$5ysV!wV!YAfZ)#NAES{ektzi907dw1p} z;o4*F_=hTDekoBh;cjsbaV}p-(qQDP%OERTxSKcUB%M<8S1q|$8)+hCb zFLjHHFG|Lhm=GbRCCAZn^e!+=6qRNYOy!qggv4@}f9sY4hkeV-6s^4pndeKW<)YS? zD@x9aKyXGY!wS)1nu=w|v=DQZi_~7<;E1-rzJVMhUH}+5?nFuL3jyU^lL>9Bu1LuX z;L4X!W1z(3(8dQsB<3?UDi%0}4?YKT!^E}xwV~SiCI0}RSxOy|kIarD6o~Ye&#lt+ zNESq6n2{e!<-i#Ba9#H0{j`=ujomF|I9ZQ}m`NR_j}f6T=KW|hH?JG|TJDd1aWZNoi4-v;7_ z!0rt8fP#{RBYVsm78}a^N<~qUm+G!yKqBVHZkH{EI)JyCmRj|{h!Y*^pUURgJOTKd z=<>z#ie6%(#gT}-c&}$qh+sSSAikJ6BH;rocvE3DXQ{07=v*T zX(CkOUY9Q#-ZgP6(8o&Bw9L4%i%Vtt!p-5;fO3Q71O1E#`$^gShV%B2z2b8EFy_x~rwd^d6~Yq)^ixkg9!_*2F*kJ zjslBFp*p8O67`T?73AE3-IHb~NRN3{H`4SOiCR>tTBN4EH7S|Ul`3sF;&ZC%F1K>? zAD>Hobgkp1VpcR%sajOyN|^OttR!S5&3Zq-8G=Pqk8ncUNGX;oG16B9`CZps0x!#sJ&?Zx^8xd0x^h z89?jY%Iscq6Kuq%&`XB$$%vCWWQh*(f>chEb1qNw;_?4MbrAmf|wJT0Ey% z3#uw8R$sIc6;+uw2}Wz=D60Pc0!ETDKo!T?-5G!)dq_dltCA9?lKUakq(w=k=#uJT zG`k_RWx}JriJdMul`06ajU0O}=Y-4!2NqoXe`#zS!9c#-8P4h95^X)q+*x*Q1rtlr zV9SlmjD0mLTJ*P-5_79FGKoZ@{SK7M#H{JYkF$P?fdv_)Sm5GrxF&qAR{j+mH`&QQ zh>d=tzoD@nF3Rr+2t}JtV_IBUZ=j1WtXM$6iIl?`jg&*oR@Hs9WtH~{{6cB%vA=T{ z_xaV4d8G55p3(N-36VQ7J)wY5K&ghzG^M5A5nwbmFwNU(w-SzqVFgbzsudXCRB47$ zYGaFaGE6hh=Klbd4I-+3=~*S4{D%JkP2&q8lwt8N!X;^P;`%E}(x*C9eHS>|5q0>L zDS<0RN+M}f8Wk%#^yxSiapix~G1V9eymHA?} zm&~d4A){u!;+#v;^tkEb35hj1$cHQzw#J#nRrc&<#-n4E(2P@70sa7phsU7v+(s>1 zkVN*}v6+zWlqaGpxk6T(%ZYGPN`lNrlOzK(rx1wT%-oyeR%1wzq2Yj#M#Be$n9v-R z{RU%bquhQ?qNxkJT|_oc)xCF>V|g^2`%BjLgDzYh4Ral8SE8k9aMGnp*D}2r)qO5! z%yb2hKoyD?##4>AbJT|v0YjJcV#xz6(O zE-en-p+Y&LLzfMigGn~ZSXZoX0W!waU&q$CMt z?-~id`U_BF2NLwoiL{~)HNtNyRLro%&og96Nf@_2W%yTD_#lf3=#%Ce;MFG)@^Kwv z77F7HlB&1QX}%;wB1^<>CS|joqcL?ZUY9N!V_Md|6{a=Lw=QSU%;;&DCM`(H3u&Ej zM(K#)1G26T>jVbpo8a0iRJ+Qp*h3Jmh6maq%PIs3RVSJbdC{^w#AGDb{(KY z1G9*u0o5WVoI#&3aTX=qq#};~k#i)XCaW`168^(?t^<-wN?b1rAma^;fc%a<2QU`mw- z(k+$viu}&e!5A#R#Gq7_Lbdu{A(vgXCi>eQPasrw7xE4M%8iRs~>DGQN;%9V2ip>^hYgo{sap|NMA%8 zUoq82C3|-PrqN22shQm(Z0nB)nyMVn2^8!MqH!rz2 zb!C`-VGApAW(jABk2dGT-XF{++?}C!`cP2oN$b9ow$8(FxWhZ?-9xH~=FN-fPvS&e zH@5Ez-fYLq2b3a|1s8luPVH@c<_~Lb23q)si{;QN^K3*+0}fN-Ja5jI^#=u+3Lmt! zdAhgqm_m+%zY}>EyE2**mIFmkh>WRPH8U77WwPThGUZD^(w!jomM}wh2#?77%40#| z;{|2=bsqq=;&;lQ-XoTV#|QWwdTtc0ptae2zY)Ytksoo(7}zb_X=l}&H@$7zUS3 z^ACl?x(2cq<;tdTv&sHgVitK(1j2fya;T_QZGS>=UXw^eUJnP_<$#SwHGdO&3opiX6y*~G-_qqm~v#dNY2anLm`g`x(-ti1zB=wMY; zLo8o%$W%Dxb|snW^!SZYSD)}eVj@97)8hG+4eg#7mF^6`B!UMOW)W_QEz(zzbhspH zH3nueAhOLf238@5N}EB9W#~?2N|h5c63nJlLQyKTY@Nss%RBF^KiyCM57UJV*7+f5_|SJWdZG+`HO$xkff-0J3M%YE6nmD3Rk)@n@I&9$-Kal z#>_JC2!$b|+72b^!6rG0GZQlOGD8|HV;vVumx<4#7cnS^XxTSFIzW{uj*5Yzh;E31 zqPs+uDp4^mouLZEo>H?uBKj20C1|9wXG@Np!r&|-gNQw%fmxQo>nRHRu?i*%5;c`k zPr4=yF7E&-Dfow~W&Z$zqtqlH!T2wDjH|y4SG#i5ETM^665dh5tLCG0#7K~Ia~mbW zmp6|@xn%92%eb=T%u1=m%&APyiY6dLF%i*Oc%Dgvh|EHvaW%fu-jdm#l9_E4Dr2Ur zH`a+nqf0I>b;W3qnU@v(b~DfenJ)m$_e@yiU0$Ga4{ z{{RLBASiYoJVg!N$c~XSE8V!zs;NvWbY&nLx@AxnSjCaIh?TtNTwQYqGl=af$iZe3 z?p}iJqD#aebj-;NCCeryOh-#*W+g9A`m*{27 z^am2tj#y2io zyt#7a%Zx`+ix)-9CCWvaxlE`t68prtQs(h>sHUaB6^QN*vFk(TGg9kp2905bp!V2Z!s@XM0y-DF}nMK`$km8KY;^}!L_L6raltfAoaW#QF3ktT~G zD2X_Z8Em`KnNdAA6B95)GL}qBjhdL2RH?MRS&dcV7V zce&w)w4GRD=#<4C{l~D>dk^aoM^7X=vFLFrUKT5xoG=(fl^J-Kq0-n^mdlqeUFlI; zmz}f`a#~~%PLhpLEbBgWxpL(_Wu-B6vqdH7W2MWNFIE|ttmw;^rCS{-W>Pw!0@+1e zqJgq4LV&DC)me&t`io@DU^iGLgaYNU;EB}B{{RVZ2rv(5J0MbeX3r#5Oho+vvt(rm zI+$?xfVn#HEdy@V1XCnmE+TO@5t)N7nUuW78_p%>TP_h&<;Cp;sItjY*ErR=b2BW zNX3%#7#V4D`@>PT7_|xu3e8?Lr`%-+UvwgN$fWH zjJy$ur+54?xnN=@A8!8uXkiCSZ=yaC6RcFP(cBydDX}?3*EL~WO=2A8js9s)H9We<}AO_)EhWDI=Cax_`(xLjxZvh@X&=66QmSYsMtPcTPFK& z!z{sfB8gw6xo{3(d4*7k$X_tZN5r9MU37$KGdckf%>7efQ*H6)0i}+lUf^a`Q6}5l zGW2!gb{-)Eg|JJ`+r?cbCHpsx;OcZP9cr1J?HyLt!3m;Z^)o1@JI&U-#C92gWfzP~ z`dP&hg?BtgZn5zgy`H3f=k<W1D{nFSa zD!XEC%FF)%7{%)r!q4Y~u@gs7im{)BQN_PfThj6*P{wv<@$@$~0r3`a#YGbG($b|$ zl`GR)RHjocFLLtGO^80v=?hY$b|{W6fhprtv6)2LG5`?!u%Zetu20@1+YdaKVA0}`d$U%j9?@N_V@SupHTz4s29I(zQu{?298RlvtCe!DRm!@$ zy184$2^=|;+KC6+C{@Tg-_%`x(9`0Lr`=>7PJY22g?3P#9!IEOvbgwM)96#-k*B=U zKhoyH^T`OK9w*<(BP1;y)_hT_laaTkNWI5Wu7AUC6v~Mkqlz^Ej`JFRMG+Ah5(*UW zJ{uI~Ii|!B{*$%`pCQz(*oLP5i?;w-tpPem6RN-X;7CoK<5O|a=8TX(#Ld&xBTNs0 z!QNUF_}fd0zR5zKUtpt8 zDElRLGJ7RfEhcr7E>)k&j>UY}XG3EY_wkJE&ssbi!-ajPy z-0vaKB7Ueb$3Rqc6)(|+HwK^W7Pr))hEue6<{soJ@aF3BP<|fWBXL5mm}vm_qlgYs zGvL2@Ka&_e$l?_6COk0zj#UPv*3mxdB(%;U$#PZgf7~N+QdTa z=pJ<~M-QZThG3tBxOiUIC*id%7u-&UM(WZL2kMz4o1r0g?S4`;dW})12RPlm<;@n5 zY(>A(hC=rX=sJ~n*{68?lcXo=TE{y;N{b7rf7!RH7+g&LB%{zF`@St2UQxsV7GzBc z)%6GMThTgM9CL9zHu}KgG%1x+w?~xRMqM1%yVN65M)BT?-ig$7a(E=Sky2qZ0d|n@ zMEE6PvzkJ;)B-`srqoWole&a#_RbVQ3M+i{)#duJ5P!olzkji<#Uc_>KPN> zY|u)B2btNphL1EUXd&@9xbz6(4Q5fs&^o8qPVHV%B|G=8%sVBqq$#Eb)InXw%#h!@NF!=!;tC8v%49V?1W( zpi)d-mV>7l1eFFIQ7(QJDhhxoE@5y&TE5wKbq!+g}0Ge~>SEPZ8En_bg&aJS+P z1&X&6hu{GM6o&vsS}5M)?zFfDcZXtuQmn;`y9O&3oIok=-Y@s_y=Sfb$e*mtb)7S_ z&z?OqocrIN7{9x_V}^~~$~7EpAY-}8<_}~!L@9aQZGC(>UyqBjKt-j;#jw1RhBv9C z6#_J!{GcU|#8#N`yYwQo*>D>I-*d%Nks8-pYW*Aa@8jDdzdl~*54Iw%J-5rgXRCj2 zmBdu#LCp>hK7BpOXWP0-S`{I0pB0S6Hx)^kpgty!QDL{JvuAJ}L;86rb8U}j0T&<1 zvyB~;gZK1|RcmoYl45faGl{xtyE!R+^T`fab28LypWP?^22ACr7jP}NZ;VyKb~5_h z_GG)v{#)-Ir+}|6sqZ3Ce;d0WZ2X&OF_S^^T-O3V>`u^qCRu5bEJnHxCKc64`;Ka{ z%VhBe&Bh#2t{^)VwfVB8<1;dk%^272k}IDSk0*mLT^)gN)(x$+Z)g0^B3L@eEpW}m z(=#wW22`S|QJVRfx*}S5_MqKkgY5KZ|G05VaBZSoFZK_>5<$y+8;^2@_pcC1{{Y#O z51sXo^ONWFC~+7Vf08rw7IWA~UQg{tFAW;CK9@NwA0v1A|zx7$bQmGKZ|6VZwf2BFw@eGE;JNyNZockF=B!s3vpzrFy;D!smQ&5n4wnl;>|{W_`&91TPm z+_%=uJ*ttb$iOwK^ID}I^jy^nrL6ACHqQdfUauArDrc>7Iqn%eF|#7qbne~)Ny1n0 z=mM`E^fRpvcQVTN3NYj6I!S7vh6C%y4q=I!{47M?5S_?jCQH#Xw&?HtVpwJ=80`*E zU|6!)>v@;De*n49>;F?y27%`gg5pMv&)2FnM18-*J&=fNH`>lzM zopf(DwBF0-oq`k3xdj`lx2R4w`P7?DdyAWdY%fN_tbP_5R0GpWI~$AfipJ2)rg?dT zn=^@~BHig;pm&L=cm3Ds1J7LkzTxbvBNf5zBMS|T5(y-6m|gG`cHM+1`u<_$8)_4w z8Td2R$q0O34$thDS(MWB@@#&ZF~u;|JU6#LmyVxu9k6C;9H!lBHA=sR4xFfL3vu6+ z81a(>&QT*R9L{|5)n0O%p*nTrIcAl0bZzU4rydODAul9~AmEo;HXHjw%$N@X5^ZpC z;LL?qc~VBRtif-poX`(p@Ch+%+Iuxv^eKJZ^3W+r9K&_|s30B3TKujBZOzFnq2m#<`zGzUp7R&%n3r2L`N`v&3sqppVh- zZkPA%rttqI4rMo09s37(#B`UAIArIl1CzD!f(G9OQ!eQ0+YDl-3~iScXA9+M&GqUd zuT8~rreN7Gq}xuA640o|P|IG5{wBK63OzXt(1O!i@A39)NwAUsq~a}Im(ir}{`W34 zNghdT?5pG)Vi2sd`W1Z~eKn&@Wt{tX_RGRvVB54f+HP?Ws}_d$2Rqk&h?2u}{&`I2 ze0X-9fq?ZcAISqH!nPS>it{-k@7bX@n`2?r!@lLQi{zs;x2V37?rX7CIaYfla zpPMhziY3c3fV)@nW%J&IQuEB1LFeI_MR zydx@Jku@|Fq^8Pn{cOwq67b9cYox5#L2QRb*b_pNLPNkx#q?Wvw`E{^$2A}E8i8T(m@0TSlvkfoZz3o(b$qQ@*{)#@A9u#{PZKzmPbjZG2^U00miF5uBINtLZU!ww^tCU#*6;cM@RK{ktXKVC*2tQlI z1I4AT!hPd?;$wET6$bmCp*!wBU!(iT%4d6p*qv=(<|ZgXRQB>7+7k$c&=6jZlHpQ} zS(x;VNRl-vJ`+(iJs`+P?L!k zJCf|~?FnX=#~wV6BMbEg_Q=>4r|Vun^P!vG1k6^jShJfP*ZI<7@zo^lOvvhhBD09k z&+Ntx4C~?#Hiq1#b=Y$!8C+$+Zwu4jL=ZSj4*r(EtNdgh`GRPdh=g^otST9_>dMvf ztmgZG&#TK&yLBv_x4N5xgXWs&eiSoc{behnM%iRo&rV!2+;t|KCA5H1L_M)xqHOpe zrY>h3AoXz#u1cS|i3^I&jz7p3qJID>l!wFH8_(A5Sr8K6vZXh+oJuP86IO4E-4PbY zXL=*c*iAjck3QHRLy{5)msP*Bz<3yWj{?_pKyv&`s)_h*WX%U#!v!&&lr@#L*X<$e za+1W>;xoEG2m0>W&LKu&$vN+Oi(eWV8s5cLoh+K{J2xKu8ZD}o5ZPvkPkX_Gj9>I0 zzYW&`6M?)JIG$3Mshl>)Owoxi0^F7#i~}j2=&x`KT-$-rDCHBHwIMU1|Mm zftixs$+8-ZDcF)g%#fz9QJa)c{ZW#+qsz2>iRKz73VQvgH$o>eAO1wOyI=J&?tATU ziTovncwpqG7jiaLrx>qXvjp5Wehn1`KvAAkf5D&4={)M(b;=LrAYy3H-h3YcK_YonlkAyB zN>2}l%6+To-}dz(F#PnRcI@T{{Hk82o;O&7T5z)bC*De0&8NAd}wDfiEdOR%uWHiIpdB|jc z^wO1mEhHjoW(4P@wZW{&H`2irCv?X} zIwD?GmugxMciHipaAbDnP>RMA*Y%J&MfnqHpV+Xap!rrwp+^`shGpS?dA_90=P80) z6S=ayGw}?1gc&Motp6H)(U_8`y^$i*KW}%gZK2&HNYIW*G?@aYU$+zwU!FGf!1urQ zv3mnK^`-W8d`_(nwm}4?$ZfG z+@sD0``j-Kj4O|cnh+bQypGJBbpySnjG|xZsVWUXjRtm;(*L0qzR~q(*kGn!IogsI zm;suB(h@K+Nbo6RcZAru&$?i-kn2T~kPCWe{=F{x_R8EuklMYj#T|clYoRPz13auWOpf}KsiE z^EN>@TIpE=B|l*UeO$H(rxF&0cI+2ax#RB@in#;pUhIHL5f0F*J6B$lq2YQb*3GLP zQ52cr7t*TCFx~_8*_m2^RliQ4wj=WX2dI3cm{O&WHRaP;r$@&E36=P`YecCBKoWDl zv}0SsDoZ_H*3Bes7FLn9dEVA7l2%o!JG0v$oM)NrTy@m3w!FjFVyZs(b2Cs(AF1F? zZ3p64Mm8jV$(&_=)qe3=`M0`qme_3hGgZz|{4zV@tUF<=^3-8gSD7eBpvxVq%}KUq z!2wTeh6Zo0*P0Wv;yY08%U6kiAJaTdEbXB}S*J{;5`C#^oKfhSYAFOAc`UyLR)d~m zz=OEdKL$b*A(Zthp2O4*9A;*WC}aP(7Ci$)l{`K08K?~x@GXbLut&0g&6B`YF@*%@ zG2Iv*M;eF&%#hbPe=^Uw;!Dhds3N^zoTx2KMmn!x=I}IizK;s$QjPZsW+m1K6}Ap= zuU9{wx@AKu!r>~$U>V*-2uMjoz=aVR2d7$p;+Ot4BTLc_ukF|<(RF~W&?#z22qAq> zbi1t6zA=pSaX2_Q@J$g;EvdEg5`7gMZU=joen@NEt#=OZl;M$lH@BYXEq{|AQ~yb}o&g;{J=OsD*;j zt*so-P%!#?^8cOf(f<#vwYeymDytZ2_a#vQ&t`?5KDZ6`@bcQ@!=UaOoK$`>ut^@h zVjn@!8`x;zL~J?rxbyW+wDq0ki@Q{fNo;x&O9G_J1Y(Xo{P?A0 zsyKcOrz*=QPY#I6lB5p3lqTKnb0;7B4OBOf>-^{mXm&0su+JN;C9C^2Y0{N_buU#5 zSAVu%{a)BFUtVvzpU`HJ*a>;8#Q`z7dYgzh$!WoJ@AUoW9_mSSSE;)S%c7ovsKMeA>_MXbI>-D8oYL7E;Pff#d7n!dDNjga-#s(lET3|mbg{C> z=$OZ5AxvOIk|FAhm%NO~#XsOBHLk0XzHa*CPr?o|%x5Inx-%w+0&9rtM1mZi4D7n3 zgnFS%DT>-G>rV=O901W5x!FROi;j^_)EFP)CZ4i*Mlv*ByI#BeDD49X@g!#@Dw^IK z*&GYatiblo_`>_D$|-#8xtWCyup))vN)+1tr8(!`SrAWX;M3 zHgAPCTIuBfeW)*ZU3-%Zc%}buyUP{TGJZy8am06z>l#GH|AsA5jUoJ3@@^^FwJc*z zaoN(RLU36OsEtVAvY-uV{C=)Dtu+j`@85And7u^|5@ASX$43fyx>Q0WrEo{?=QCuT zn)$HU@`jgXNVdi>js{7Q-e3!>bMG-Dukxucy!hz?bz$g^P*|{E6Z*l%zDq5hQVG;t zl5`jD{yCc6zYj8IwaLAyZB$s&<7_N;t zH@mU-x_)Vf239gEsgSGW@Adop_YQg=+#4|~_VSP3T&d)2`hzMrQ2bOs{5inz@c7E)iUe0YQXkZ{ z^_-x{iAPTO(CYhS4^4)2f2S`Cq|1R{USIJ*Yss3Af<;Ue7IQLU58BRnR8|J5l0JxH ziiT!eR?F*8*VmxxWLfwEdMe93bg2ql&o)AI2SbR%4f{Ca8^jI?9};AqXRa-A<@Rpc zB&arj$xG&MJg>M`FSJCqtT-?|on=<3 zr?T5I!71ot&vIymkvLYsVJLQRLKTZ6ZX-z?J+Pgs)=4A+L?((9>2*`QCKB^4uzQNB zuO8aUS4rKBHeB@d^icaXHSNo_w;GVXBlYe%-r2ZhGun#zHL^gDcfd(hu>gkR#8J#b z$|s`@+lEzYTg-fFliwWtyvxI6j!klfHK^K02g{*LeTY9|uv#HZZMkUx8};1uvgKL+ z);i$2$ssHD%_xd`S5Xj8i{RZv2F{tHNhCog(2v?~BO?i-ow>~t`%;DNXH3w9P9U+; z2af^n_(lmRzL;=x-IDlQ>CzgOeUeogwzWi(n99Uf1s?$)Ig-rB#4_IisJC9fyt+*; zft83+KiEfMCPmrq1kJ5t-GPi+5&?Hd`eH$VrM|9QscKw~mJDZfSi|}r@!?_a_qJ#F zKfn7NHOZlvgx%V-zzjph10oKP;#N*Q-gGyWoHHfAX~M`ABep#Gpkw+L7TAzY>ATm3 zM4^2d(`fs}O=V?%ZzBci0)A^N7Up20JbwCDl_}z)4~=5&@w5q~3=ch0Qd7M6Fr2+y zH%VX07~Ly3XSAf>zazxbn@J%iN=iU$3~7=IP|R1a3nH%?n|4BWaY}=i#-~@2lgEUN zL+_q!TV;hcCgwv_@XMxj21#dQQ^hMn?JE#$KX_CLR!u#*PAnSl&z#9z5K<%koJ8m^ za=)~ka}~F^0BT312OaN zR@Q1icTh|Rby-41N^^;dQgEGK9lNLrFwWsQ}3aPf6)AWq2>Fsm4m#tIELfBIkmR#PWwSf zb}d>PbN@5dWvc~K!-I{VtHZRcPFblR&4%7O4;@(xLQC(Iw~j!Bl}I{iQ|hDN?&+-m z0B(=V{{WNJPj9X*Z&k@qN;U?;@)S@9F84(m-@F)!+cT9_JN?$h*s@=ozMOiEf3iI^ zapMaUODe2Fz23WbLio9D{6dQqjj9SEQ(O)>=A!yPuo4)FVMbZPHg{wN`!E&{T?yzc zV-ZFzW%z6aYU=gNq3RDgo*U~{Zc?wMeyl^QND~^P8_+5yXFo;))LlPyGsMsH+-+I)E7N*GX(CT(!h|B7DMiOaXU3@N03L-8pUg| zwwt87tug+6JdsZ&(#jSLA`{fnhl#$QP88qJ)?t#Nl8zWDAs&t{&D>*bA^Fv{PR-83 zXO9#Yo684c_DlF!n4(1Syd*F&KCf7cuZPc9ouiANO77VjKmCBRlu7*1|8V!^!~gBg z-h!oGcCgT=M&Gb2Dyh&>bFhP~28i9(07*s#Go?!or+1+{cZ zrO_9%UJ6j+I5B#Oh5r(KZ1IB@sjeXFJMZiP00I>Ko%-;gx3;KCwfHv|{Dk zzo*LS*$x{)e>)461pWPjg@-mL$r~FcPj?vNt$b9tPa+~r% z+8D5iF#vs-0+}yHjJAo}!xt&vlh|rs-JTFgV$9M%u2rK8?a#)_*_jjLI(G%YXBj?vR{){=ud}+F(e8n?*2sT77{L*d~DIMJE zt|I!UAsYwf^&&B*5b6W7)5mC8%mOGx4wxe;?;SL-zGFpsz-F?cYKx-&WRgaBx0*ah zd1n3dzDLBDoashSo_e|4eC3SMDeLaATJRgbmNmKP7S8W@Mn0t4ExWoe^Zz#Iw~MQz znpo$XZQb$=6ChpBQOIvOT=xjwA$}6k~zc=Te^yg zd)T24t{D`j3z6AS3ESi@)3i<^mIjAg-?o%Miu#Ttj=p3c=gSj)Rr1hG2SZydqc*-_ z-=>rfvO^1BOj7wV3e(@^qRaLlfnr9;nF@&h@=tendumJvEc6f@J9W3>j7Df5vhJhf zFsN(z?(TeX($WhfyBoRsshHEWr_bVGSDM%#dxh!U`IfAm^*$v??1jtuFfq08o5=iX zDKg1PEd542jYf$_L?J-pSk^l1er9|Eo`qksS9?=}JQ%@sN@++-N?1k9Pd4=dXz9s(iB!mrK z!pMNd?<7pFQov);g@N{EuT@C+arg1Rg`qR7bWa_iA`K?#oW=Cwa)-ey>>{}9@AY}w_EzWfOJl(9Z+YE44}4mNpF<)%)48n^gGL$6u+noFSXP8^F(*M^)fpb z_kaY%Ky0Gaocpg7??pg>xPUgT24B8PPIG?w=i)p&fhu>lQ}CDgRg`%ddlk77Il_LJ zTke687K`DE%-Rgf8q_j7#%Dty=lt^7a)y~0AU@YYgy8O%Im0)taGLAG^0=1YkY}bx z@#tmf*-Q)2`x-Vz9Q{qxO)VLWE}3d9E6wE-Fk9w2G6w}#>4g=FLPzAMF0Wy>A!2wJ zdV&)Yj-@W?ILe-O%@?}DKl)fgesx6*vGcpIisP{{GVlfN8&FA)OT{NG5O>x7@#dik zIH+r`CuT_4>J3wE)1C70?7oKWjq)&%E_li_LbQU3Es)b-EVgefm%2?}VB*Ll1g|p? zC%e+?qDER(ii~)Y;HfyG`=@Az$~@GH&%V31ulsMW-1vUP1#N*_e_jiN!!Q4qyHp)G z1vNVr8Sw$j^1yN8_y@sN1<9AH%fvk~$VwWTL8-XN^-^E|VX29MpF5^XShsnUL}HHo zhz)1o!6BZJ6ydLX!We}F@|U6-mbngS9P*)%kymZ4QfKNYnJH1*^aDEc$pORaX2Jsy zhqF0=qTpoXUhqMo0IM4Mh}hW99TZ*&A<0HVOLo1>#{a^QQ!@@7?j3?vFk<$(%`z29 zdBv?FqgW2gQwMoXB`DvM5)(UxnX*K5h0xPL2(F7+(qfg`bUKDN_YQC!q84D?OK|7l zS7R>Ioo)vCVSE+^YRp4VzK1~ka=SqGaU`D(74Uohp1=(|o_mkA) zor}7^CJ>U8y5qkcqpljm%MF6C$UcPNx`;s{RBfp)jm8Y4Yp%?kT@@X0S@bk5w3}6x z+NKs&5Wkx}fW!m2b3SV&;V8Z8tD|9J^xA2HuNLH z+cNI*?l@R1b3|qouCr3to(= zI>)oGJ5(UVh%bGC#leafnRcrOw~Lbj`A@nUk(|4KaixvW{w!Fj#*n+Ty^DJ3n;bNVx?gyG+* zWD{r?C@~*X4>>_8kx7um%#RG%8O<_EY8YFe+LIRLIm(DQIjv72>ioX)M4{&LI<3>_ zRhEa`s}e^K19k_vEZGM5XNqzAR4n658MnKq>vUz$9>k`SZi~7M7}j~{Pap?~3ZL^O z3giiRTjjoq_IcK-6Z1<#ksMSN#wT*meXT-}#d1hJ9HsodF3wy9GYj>q+UT^62*^lO zGQ5dCU+`SeHUb4@O7g2>~lJlTeKDl1n9leC zQJsz}-y%1C1eD)Z`(hb8qoTao7sh6vmENz?@`}5SWwD7cV5(-5whS7hk3#q2itj!$ zArIBkw4FF=ub@juo!YJHNN)ZgpuO{cS$!;e?`8EL4h&t5bgp%j7+htE{;p6+6Ug61 zg(F8ZvDho-6A<#rubedcfAnfqRaq+WicRQ`*haf!;Q$5Jw{K0t>}qG01nsnztp_W{ zHj_BxmX?3${2E;Z-sL?R$a?Kkm4|s^o|v=!S@FY?Xs@~4 zv_SL(Do7sk* zq^;|$b5Qz=Fm~7Zc5Ov3EL$@kYjC9;#k{y8A-|VE9HA;YX}QNp6jt*JQ8E4iNtd& zE#L4TAeiFRmV$JNRNct_h_vp@#kjPQ%v%>sO76yZ84FAPcCm|%Zjy)b*2ZuHe(lFl zTXqqn0mrCIyYlK3^V}xX{LDHV6s(Iu!PAuQFL#Utrt4(TQZN; z**;>vbHuTj`OGI#s5khCgAA^RiTPeCF_to>^54C=z=X~CBDNh*7HN*Lv>kC7)ryYv zW@T?d;12Ry*WljStLQVbocPE>4aViF?P$evimjjbXc}&)$ZwgtadAIC9`Us8BjF;j z*!`hnSBbNU<|Zx$IE4;v6Zjn{-BAecB$g&o2;w?5oC?ZdK#1#Os-aP$m_|% z2mP{R*Yic{D4v;hT(f5TC;MPP%K<(1GM}XMh)T{w;e)%TsGTNPaV~VU*b9?f4VQuv zgt6)G*BEyC0bGE#P8V*NM?t)2N-65nHdGEhT~zq1KM3@{};H%#@`vgSiO{S}sBM0H_@pr3C9j*sv z1TNus4#*aDMq=Zx6=vSDRW5mX7&Sc-zB^(7-VEMpt=BcI**)ph9!i$&Q-Hk49|<2Q zezEQ6CZK_p$v`!gQH;*8l{TM^sXT5(0b+;N>y9N;Jp4=hhc6$`Lk%~G>5)?Xe z2e>Z^L$LcPO}mz|c`-HzKk5h1Qt>SR&l=5%kIlhRh;>LyvD$WDE!1(cf^3$W#uUP(Y|p zw*LVFV!Wdc%1(LKw*1o!9~s3i+D?~5iE4u>3uZ~@R4bGy^nOx666G;xN#$&E>t5Xr z2UmrrubR-vUM$K!#OuFzhHGpA*-o*dgBe%LPvtR57sx%0yich_{sGj3E7%%YO6J6a z{}lcMus(>}$?jV|71lZ5rJxLMhcB9?GgD(x)jVYcF?&V8{_lNYJ>U%{OXj0TR8ju} z6vrZZQMLcfPt$e1YyVlXG_F9~cf9Ak{&n`QoUO4qrszb567=um#Di}piJ4@P#V8|!|1*Me z)K>V=P$bG)o*IkgG+6b%k?hiLejUUhZu_VQ)(*ysk6Q>% zeGZ+@fkobk{iP=vs%!_(Ni&vG7%=CboZ&sO)#qNP`AC;&X;Wlvz?l+UmQXbR#`INc z67bgXL=N#&6F1ySfz3KCEfE)UDg;f~O8*R7d6!8pjjbtH3q@mKF{zDc9?^9lsszdF zzHdmZB=I>YrOpbiBp{VlKQ=?{G9gH$Pr7JOc<1k1wwkF3m^9=qq7XICE#YFLm)@@# zm2UkIaml%MbBK%vL1L`IIOSNJ5ugVYUbYP(pMTx`ndzdWN-aps`#YMF0nx$h$RU>v zxQ) zFrwmPF|~_Pf>-R*l(>t9-_V?Y6AbT+Ub7q4KVBFX{0d7tOt)bB;puSJ78B=I<%Wq6 z^B-VL+|yn!7KO?o!$gr5z#DMk1KIjluR}%K+mL=b4K|WD315_$0JTl;vSq;}U9fI@ zd^gDw6zN+mt0r}J14Ax(GqoHz6{(3;e%UjiUb8yA*GZOEw(k`bCFYYUudTGawF|_L z-+C_0-J9*A$ji$=*V<&TkC3l;5jSI;@1!=s)Alh0?WlpZM;n)LO5I)%ZSKRwoy(-f zm&z@#ywFwXm`JC}7QFmIkG}n`Foa&=LvOaDXFRZz=1Dh*!+n8NUN5%eSCQpQ#AmTP z9~X6)>fSWuZTr!ZmwE;6_se%MF=zRQ>exT%mQY{UWcw-Fr@TFKh;h zEzPwT;my8$f;x<*-qTkyksKyu(4xaVFJhKjp7oo1>iqKn84H0BKD`{P|8UBG3y?ip zz&=Inr4<++(q9$!nksb7D<1@NZ2{XaP(Rm(TO>u!*56^c?@=5d!c33te?y=Ev?&uu z+?BGlM+cnE=<)vmtTH7jBI4;XR`QbFpC4mA0bt}WR&M@eUiaGyzI#K=z|1PtD!4`| z(f(a_%m@ARJLLp{y1{oYlEN-zAH|ryV;rq~xUe1GLX!PJ zUu(WWa0f*^!aow!MPVBhy*4#-*0h~31e7SLSz)|7#F0j99F2c)7$X~do>$XbTmE9d zjIoqPYVaSx#BS|FnX~Qd%Vwj3X;JQupRspwG4=H53}ho$(lz#V*c%oLu{>-zET8Hw3I$nhgln;OBUi?Xu6;o7=SUYk0j0*AsQfLV#lCR?P+qwQ%th2 zZ&LG`ZpE^%lLvn3wb!ByG&&x6FxhD?PxJ+gv;`k~iA9>yA?NZLuqpx9NJtDu=SNWO zc%4;J_;BMg9|0dZ7LcJWsoW@X+^5~Iv&VoHIGHHJ94cv%XF-S4ttXmmEJ1inhvi-e z)iM?z#)0t8DlppOm$pCotKT4Fh{{CY1E5QJ#tf>ElsD%V7I8GPF;xuh3F)ySs*U?6PF`pGJ8X(7{*{2lBSp5RV4!84QEE}|<=c|)xa%Rl z&wc#yF!_KK(4xX;^qjAM!*MR(Qpj%`1oXTZI?5wALX$UoKQ3_vSqRfVTVc|ICN<*QvWRJYCQ_1R6c_3vYzMcvVfifQ zh|6POwxOozk^=u)PM?ofOn?dPKi;F_dfYF+wLV#=t9VKvykvhe@pag)t-JWWhX;YK z=Iu^?0{if^mxKlLKgoLSX$2l$Whw-<89OL^a|xs%KErjuAFAfQ|KV`nY&7QogZ*+c z5+lgEgMnm_BSxu3VnIiVk}#)U1cC2aKpwSg8=#Q%)Adr9@QOkQY|WliPk0!R7GU+J zbl1O3 z9D7CIeTEFnGq^W6*>B1wb@%&r1^rlDBOY;{3pj@~--wYBK9<~aj$Ajl^dMN}cvRmlR?%isOHfqdi zoM3`uoP*7m5C&9JBZ6CFm1~{6h}nGX*tB>4lHyqpCA*CW+qF}e?&4=36A|0DhjHE^ z-`Rcw0@J^oFnj$mvtJ09J^b$|HF9Zv3ioX+jF>JA+OGCipaL>3(+_03Z0J(WqWPPe ztk#v;{9e|%Ip8`ZAj$~JmQ|QJ8sk~$wdUSn)B#&4zQ{kz>iEIsE&318@H1QEH_y-k zXdn_o{V+~zyBzx(xpoj$w;)sc-Cd)b<(A8}b*MkKDKb0ZiH+*G|Af2ql<1XcyMIdO zCfz%Y;BMd(Tf)IW;Le+W08)r}-qd_TOu8?-tNV)T`CW|(fTIh`Sdn7vvrl|3sxF1c z;7Fi--$PIGXaPOZOE}(E%qH-!J~}-*F4sUT33R-5psa0SB4l?JI`vs6Hch0c5aU+1 ziHJ>X*U~SsF8URh-Bi$9N1(C4l=RV6eA2So_#b1*L>P02(4^kAW7!1GxZ#(jlv#|^ z^)5uGmT-_Eioiz=;?RdK6MSm|nwn1%m|Ph)$m-m6U|-i77R|;lb)l_(=i5!!_@L0oy@Z=CyU~eC zzSKIlG8>D&k2)-jPWmzTjQFn;-8fHH%le2u8i_<125~9xN@7XjXI1Yz zw2%3k>)2Y9$o+P^K`EmhiCV+tZb3)--jy5PXXynx-JeI>AZ>>PJVVz!Oj1*(KN&N1 zp6QH|?fCo8#`F8)ByIGrE$b$ITRi`m)%M-xe8GpS{I&k;)sF@^J;=X~(jxfEiGL}s zs@Q4|e%eD>yCGstpH)QcCLNwpwW5ZavWYEM%voF#@_RhiS{5*Ii^-*m|NDwEK(9#C&bL>4p+=FJR4uOLJWneBaJ>`k(FH*~QRf zX38k7#hLc=ACZ-)GeL!iwf$-v4jbTDt!S#32|%-mTPe%dYze9yIO41XhhS3=!Dp*o z{{htI{nrnOyVf$+R5$K2@B!QHR0 zCGz?MUyG>@ZAe@OWiD%o)XQAz5&v0r3Rk?E2Q9YUd}HiAL{$Z@43a9F9|BI-{sO@K z>GcuRBPdB%GHl0MN*Q;;_6GWQyV8P6P*%PWB18>z#6+8TeCaAjxeh7$R%x7C+@cF8P z+n0(tCVW~&_u4$Xx(i;4*q~gQMHI20gc~O6CT~GWpF%}${?PG|=HP$2Ixm~;Q>Tf0 z0927%6rB^GDjppbZ`$XV^!Fa zOS2k!NE(HnieRv?x*dv*E(IVVN5?SExSvF~tMO+-F9G|ev(8SY}6#)pHXuSUn~V=yU|{GNr0t~&CEFHEkhicR_6UHRJeidy{GnhJ0>Y^ z7u4uTk7hb2%CsvM1Zts~8$5%E9DV|Fp?#9bchc6SCb}icmig$Hv|(4fO~(GWmPgH% z9%zqqkua`5@rOxu0UeDf)Drt+{_|ZD5kw5%8Nd|9505oS>}HL^8H)g!(<(X1oZJin5|42Ct1d{*iwFcNMZ}MWkhN&B-R21UKON5y-PxOr z|8#kP(;i0epvoy?`W*SC^oX+xTMn=o^`B+#SPnj~YgSAG3wbmRopf_UmD@k!XML;u zD*DHDFCSMF1;_`f62Tl`?Zpk^eZCF&8p1&DXegJAYW-)As4fKi+AK#6R=RR5Cdw2j z?Nzr^wjpnNh?vQg4CiA>HaXFsYmif+ZQP&9cCsVkwG4h zIKO;4e0RPByT94}w!}%_=+GJud*$Mj$AU)PbUTB1d`y|8!`eJVw0%X4! zedDy{c9|-UVYA~UtU4$~xE=x7c91Z(XJC{b9pS2p3q`Mnj(q&K`nSOzLSg-F&Bj5N zzerG5M7}zxlad3tm^sGfDnW-Bren7S2Pt_83;hESYmTpuUR+a(((6!JqlzOeH_1Ll zjHRWeHG@n&v^Ua7sQD47@|zH%!gAWSAKvmQ5M?&063>o#fBqq^KMWh~=jM5Qt^@)m z_@aV#rTmJ#n8vf|HZ6gy9J79q!=oJ>GSwxKFp zuIGz;Von!X|p)OKtUQ(Bk@T9od z1}O^ou?l)cN{}Iw;r%cCD*6Mc{8dgt#d^<2AWzwa1Z5OxapEXaAcP>jhl*ZjVYcy1 zf-+wBNaL4q@MZ#*@Bp{*#t`bMIV!y{{^I&)So$kHBZ@*sug&ge_lKSe2besj1@aG^ z{PDQjj#}_aRj9B3TFECW(NtVfY!!dVI$7~ZhDp!(%|E9Y`t@_}f)#BnQ;|&4x>?x< zszV9dQOo$*b_6H=SvEFs%v)>NgeV29%3QI^%yIt%`oyTHdDf9I&cO6Nh3;4ZU);?6 zA7IhDJLs_LAAn;GQvlW@8@4SFllby*K0yrY{ufp3upZ4BZCXbJ_{l1AoxIL$ZM|>6 zcWD<{Afmr3Cb3$X8nC)fmP|9~`RkJ7&5EZ|-`^kA?oj|Ur8ks7v7I_^)|ol|xnG91 z<*c=s==5^(!e$^cSA5(s2Sx+lw@a$cGYI>eyb?PjGfqc=<{=vc5yMtExn^f1;p>c{ z`FPd(zI@j3#HKQT%@rA@X)mbU%6(iRc9nB zx!$-CBEcP1Q3!qC2>;M>!icJ+&+R>M_YWq`pZ-nOMMdz|b+Iiy-?F&>4^YB8r)a^) ze%C`+_a&akrUx}a2$STESWkbfT873|@|7h@CudJiaBh~~LxZi#J+-Xjd zm-e&A;`k;ndF*gw5l_Z=tnK}`%ofd|eetjg)`Td)7j5he&k(I$7GV*rt2bOTqASwY z%4m5^&5YB)9!bd1(UHdqE&TSwQ>KIX(2#y3I!U&GI`bWUsEAzje)uF-qbjD3h#SYv z?BZUx1INvxheGYTl85pbU(1#Jz}0pv7L$;*20{);?hE4+$)AB+Jz>MWTtyvedlYLO z=~q1PH+Djb{k{x+5t7p8yOQV3~@HvwY}~HO;|Xi8}a)Oq)M^$;T$mz zUyNqU!SQGc%BG=77yqPafEcfGG?^YTEK%?$adDCw!6}d~OYVY*TuFK7zik)~T5umRbHm|c^yO6&LnsV)9ckm&8tp3#483n_$5)8Ux8j2s;aZ|W^<+! zCwdYZnBlVEPu83qgzJnf`6mzMMNq;RD%Yv(48|E5%5p)Xnh1w*4o1@SG}q3^t!2JC zHtKc`r~QH`-d$go&^M%90=4;9@9xjB_KHd~2Qb@fcg{m@{((8p4XYpAw4mLW2KF9 z-rwpyjhtiYbQdl$_v*%{gmm zw_!($yK<1ke87l~$4HBO-TXrNKV+L(+K!DNy%Noi!Yy@34#)Gcs5c%}j>bV236{ z8*04tk^LP0)gmy0pD>Nq=_rzMb|?Pj?S8jSsq-1cN$rOGIUmCN{PerF=q73SfqN)w zMe5~oxVwbf@B{r3dIHwne;~^TV-7V>qUR^aL{>;dFw5e2;Cv^aYRcqXf$tO$w{J2G zMny*AjXcWEYzY@;G#$smosy3>L$`8l+75p7N3cJjPm>YLS_D2sd6e(e#y$wwS#{5r zlhNXs1dl(x8&&2gO>Vi`={L8x_dlZ)uz*|f{Zn9}Vi25y@jb~)M7y^n_$Km0kwmFT zSZ|oNkgwNlH^1Xi1`7n7?L$J&6>bmA)r-d6jW-dovwYS$;sN~E5YyEN-OP_|yX$Z0 zBFwbSK6%E|Zo{2x-#Q#LP*VewmvfT{(eJ0lzgN^(nn6FkT`5=0A+p)sApSUyy zC|4RhLUn0Er$zTC9QtsM0o6G-q7bld?mpu%GT4GJ!l{_7(*nNvC;s?1On$)=(a>6o zRRPY5PhOOTttYHiud0wugCbUZt5oBAd27U~$6ZWCB2K6$eQ~a6$vC18Rus7LE8-Z1 zfGuU5^sS^a*dh3Ui(lL1As*HCujIjfpHkV!RnCel0%wyMW!d;0&=8F} zXnk(FizTB)FOdv}H}n{oNEz7|7cCoL zr&>B<*?cMzpT1tcKrC!#1n9Q!(2ULJXQvc_{wx&fW4G z=$+fP3%zltRuo!#lQD9|g-vn2@uu~j2VI2Le7~4q^~U9cjEeMdsNsc{W@mZFrmwp( zk*yoFN#79Am=1tC1NLtWO)zC&*<)fi(J~2N>oVDb;j<4tPL$sBTpSuK6bhuu#(m(3WzvG|XOB##Is*pppN8*0!#f(Rs#PLdc$ftl=D@F!ixAkyy zUVF#whco_fU$j6R4i0-HIh;~wJ9(gFNJFzeP}#}g_OKRL@#@6_j4~*Rgiul!2rX%U zwo+iEwQW~ig*F1*&GLM7Up=HD`R)E0@&`+V*`V~3QZuK*${ z+*Xth#~%1{FCZi8W-jCF!%KAL-;2NZUA|6IjhSFP{AX_gSB=I8sa>?8|AU=Fo!;~! z;bKwlLi;X)a~T8Y+EZRKgFJq)Q#;dzij9qik3ka02TWx6#qrx3r2% zN4o*`{-UHk{Zg z=Yl9J_bo|9?1G}Vv($XtVqQIxOxPi%^le$cNe8dpekRV6@?*6yUolBu66^65Rin?Jro+ZwoE@hTm`;SZ<-rD)cPNQ6G41!F>%@@gLj|$3i`kdj*&Mp!A>_3-jO@UREd=DN*gVVwG-3dl0>n zO&?BCDfOz$lD!%g#up!RU{W~<%J#CPDLqnQi+pwdXu+A7O}G5aC{_eSr!2Txw0cpQPoXcO2p8@Y3jAc~%N^%?yQJbM>Z2Mq zDiU#D5*_E7``iorXD~pP#eBNRKM`^i;27<<5ZWu4YH9cDzqI0NSGHS3=pZDqINkTW z4_y=)7^<0?_a%rU1T9Q{26n@%`oDyy+BL(uSR;V-Wku+tJyZ*_?HAR_PX%|1^LQ?T zBsQNYK&}hsg(*v&$6I{kp0OIQKW)C73g{y!_l(FtK&2#l<3D^9PE=4AsC?tynR;#< zZ+`rYeU4`@u}zh&b96WSG`f^W0i@$QwY(@u&`J=V>S5Xa{=l2$)ApI{t9R2=lnl&2 z$)%O4K%(5C#lET}VUixCsI4bwfb#Q>eN8__*j4O>B$6`6o(oXuyc5eZvIT_AOJaff zE69!dd03Wp+0i;Y?Y>*|#UZ|KZ{gQ|Eq1?n!I;1n;DLH!mf+HXcdmckcIA8Z)4O?K z!_mpG^tZi6t|8WXDsGlm$5%ksf4V*(TR?R?u#%U)YH0|?i=JXu)+IFxNL$!wg21Z_ z{qc`xi?-^T#(c9(J(e8iHO*2~QRqNI)6YuZG3`R!A#-dt8~s)|jHE3N`Kw(2YLInx zC3G&1)YG34chxIu){hz~2U)rR=2ioU&-MuE148nOlCyPX;wX!jnZ#@C$tT?~PLZo$ z?AzZo@1l^)4#DERvL-5u?^RJxdtb6HyM2Zq9NN12Dt_UHw=VP8>VQ9Xq1uyKZz2Cw zpd|4-T|7>j5FH#yt$f&)(_9d#-MQ${@4tdJR7a}JW-^|5Y4j|TB`XxEL+hI8hBSqH zPSdqi?>@F|ABHS>E+Z?LYM2p?O%D0_5EuQ?OQbk7?pRw1G&0!-)+$DNsSXTDQ^D(W zmjm;IarO>|V{$8RmYRko!0>PCuNxalq;}bq6yvb3t63sVPnV#nYn!n7qrIf9p70Bk z$qoVCB^nZD1f@(RYpHR5=k|*ccmJP?u@GdNk%Q3v=KP@0F$a@`kc+IHr+Yw@eia}} zG;68$8$07a5PiqxMCmv|hwCe^Dc%KKeo3!apif>3h;o-I&L$uFr$T7%z$xR-0IWbe zok&+!?-DiGqCDh6ysUkbH(~ubn(?eVn)`i zQmwR+t)^fY|92JwHpK))Ik z3xGB>z{(Ou80N9?0#p_OjH+)q*j|ja7RzR$(R9AMI2)EOR_*IeI;j9&Tj+i~cqVVUSqaCOpR;PrBU3hk5wrO9uaj4Q8W0u$?&wJoT#%O>z$D z$+mV_LQS@~+{Mb&j)`C7BDCalCNEy1Nph)RuA87VCn=M{$wWn5fE<$_-pV^kr2@*VQm2G73GBERrJ4E**Up-z zX)vRPQ&?H=C!5n`Sy+Y+z*jW|@_+sH(tM(EF;QkKdh*1O_z?o7DP+dN^fJo{vv-M`M9w28OuA+(mtRd;Kj4X;(ah%TX z%(sT2qA#h#fI)xx7TG2-YX8zcw(<*%T)&?i5`M0+Q}-5y>5PA$q(R`1!)8q1&3_;- zC<(03em}?KKBoeHkkofsIJD=KPhsFGGih_WI z&(s(Xq#ea#q9GO!rkEE=~4*s^nhaIho^yDU<^f6NtHFB#rd$rl0QcBBcPv zayGy(34mjkA~@eyntzGc?-#DW!faXtGA~-RDN40!hzXTN|#(A0Frrxu;#D2ChJuHQ1 z%We3u!U8J z@a1cdyajwYz%z{ov1H5&^=!yWT|6JcRHim7KEYU`c$OkK%qngC%{>;*>aT!s3P^|p zZ%7!F=y*jS8T*|8t+SNH=L;bOt;Oc|tMFUn&vM0yO^ILH>ly@yt7FU2g*M{X`~z+E zn9Qs8lfA$FA!k*(xQQjzN?`ID4BUi1n|E>CCg}c+2)qE|?K~yX(4g$ISiwdh&(~#> z#47FR)?dRf@yka*Bd*Dm=71{Cvp$+VA70HnAvAFWoWypZ^8QyADml~iBCqQnD263^ z1YRf+_P$&H_3riFysk0lq_C*_&;Ebj87QZ`>y3_n6JJW!-sWix)7sOc`Pwk@tQj$w zRE4?e1)kxFuTObR!}+wC>ulR%yH98ysi3*=j!+QKmxzm&#-60gy0WT#;&lKc7Lysl zd9t3KQ(j%97ACmsOOE5a##T`tH_-3c(cK65Oh>z+ig?*G+7!GC5cF`X;<7QJC=s{C zeK^NK#@{7(>@^mkepHY%l_nx*ywZ-o)bUv7^}hCO7hsXC>7t%1myC3UnCmPYtq z61VxFx=o#3(d~->TG?9HcB~L3+P>3AY;i)77{I{Q((p_%sp8uJdRHO4nwO>r!4y$<Vbvm1t04C(iju!>71HJ9I=5qkHH~%B?L>s-xF*FpfwFa9V zoyTc>3q#emopggb1#clS8ju`ZlGEg$KeR^JZ1sjt<^Kb@wHBN_2d~QuxHj+^B`pW3 zK{ejrB_y>Z5R_vwSy%98G1R}@u*wc#7Ss}ZM}yR4s*Ib`Hi_!IYIy_rpK{Sq`D7n( z(fBK~3lRgo&UYW{P3!NBkR*R{qd)2WPW*Vx#&aL}5=k3dl0@Y*U%Q$TR&IG*Z4}% z>*+|7@w<&{rWw$pF6)G$awTfL*z95dftt=j9Vs;f$3EeRF7yhcC3J~OF+T`VQ?}<5 zWORo0_>KrxC=^L>E?7VsdMU7d27+dblQV8{~ z&&6SEHvpcI^TyJ&k&=j_8NZzIdwA^UvExgc;OU^k>B+lw&opqXi>Jl2gZLBiE?@@j zln%h@B^Hg`*&5vi&8AKz)FOXSwEIo$$xi+UQYTFZf@#RxMRk^z%I+!s8#%G_Neu)Q zDuerz$ELRoaf!}JE^=gHxhy-~J~OnYtUDx-|3LPS$WPmlXZ<_0hq0u`;zXeNUihO{ zZR9Fzp!M#H%Y+ayQn+}}HP(Ei>H@@HEBDrR2MTPIR)23m)uK3P>oF=boiO)0@V5Rn zp}m7pSReCT^3Z&Lo0B?-CYJvjlqaC>&3W)(6jkiEcJM+4U|7pi?cnv+zK2@`3z;5X z!1J@l$3JbTQZaJ%@o8r^z9l1X_azz2rs{E8b1lG6&uZ=vPeG;W>-urb4uE6aw*D_p z_V2xU5vexi-DCDl05Q*b4=sN`L|%vk73_&_b;5S^C2OE{;6ktz=rO)<_Rb43e$QrG zjRS1n{1Yz=Qq8xn_Wp=QX>yY-A}C*o*d3 z(=Dc@=XtbIHEL->fa3dAcs%{Z~Sh71x;^hdjtnB8+M>aWol|+ z2)yC%c;iO5X8@rQOdW7sOfDMgrLI??ArM%qV8=jo09lVYc-g!z8?>@+Mzdy^x~l<& z02&JIU!^>7+?~|{JW?7y@mm9>By4&V@JdgfMwHf_!b97-wz*cvKX7RQc+F8!+&(^(U^?OaN3AR?axj9XQ5U$hW?cY2^^Zl@ zs)El#uap>B^dHC~WNt5#YT^q^(lS#~l|b^^x2i5L)0A zjrlKHCvKZRj_d0t>vRTbD*u5HdSx(v&z5j@`m`som+^`46}FNv*=N2{%^R@FEn?Fnpt;HCY0l# zl25f^Sg>}SYHdAU=_dP$&chhzw8QD7FVCHz@ z6Wmx2jt{S?SI)Sw{Z(yhQRyNGA#-a*;Gyt}3+xOV28~@0hq=vpVLGpsgl<$9UtRA) zx}Oh@R43;2McgDlq(HZdf+pGH^GVG#hJKZ z&E+s)OsUU_05$aex73s6-6Gvm*h$RBE4cB8(c&LF4a$|EMl7MaMXMkf{(U%e^WR z#Mb7{ZBtA3N4~sl6{y&)HH?5Lb6ndW+FR#)?N_Xl_L+X_7_cH!@azBnsZb>TLI;fxET^O7ub8d*KGl)ZK};|?h-5Ej=vT})pq_wJU+Q&8FfD5YC?zK zpFc+t6{&m$0P#hJEGu@#N8~5!?Sq(fkv{^HAI$gTY$jp;vLuLPMFR(gYtBNS5}iyp(H>|@j@!2)NH#`4){Y3;$i}J`@I<`S6((+)_!VXMFVIbkAI>53g7gVY*X=A+OBKhUxlh}rY(hJ3tm$4^5 zZ`Lo2JGxf8IGR7NN|CPq`;brkskk}mD19U-uHQtce*Zzq9K$I+JuL>|8)j%ltynt(aLotfsE-e=SUa1}Ohp znEYvgp<93j%!QL|f9)Qe8jJaeE$J%X(;gdhbV9gOHm}!DjlrFA|K1?npRZfp`{%uC zDKq(7E{(-Mw8)%@5SeW!k&0S?_U+4#h0@A~jPgG9Rn*KPQsVBc2Q zBZFHuzmTD$M8@BAMCyu*L_Y}lh2iYFk(;5zKG~{h#0-Dtzq9Fm(&0N46s#69@gbZu z>if-P0Rh{`apV>@;rYd?DLD#~@o_KZ7YUKM@I+Q|60ueCr5{kESLJo(c2(YMUc_AX0iIud^r>p`#$$rwr24jxx@|O-}_jnVtNBIZ1DDy@6c$O zI7f?cK-DqqM-R>Ov4e)t^v0cP=pPjL%4u~|`fTPfuB->~YtI+6YvJiGR#r*Av0W@C zru^uEEGl$H%SEbYIg*XfMvupYH_ef`~3P4%sU z1lx^j+l)I;X`gyvN37+QPHWjrL*g^=-#c5I{zC?i<5FtKC23-~NrXWm1j)i3=a@Y0 zD=Z&kmAQiHFU8jw7_|l`IB)Ie<*y1l`%BUwEILC^|I}=yEhSdqdc0igs zYGO@hsKRkwu~e^kPQ%F{-*;;3hicW zIKN8usy^^*;S9leW7q733L-UsZt}9`j-hBL72m+~;gj-C96D9Q##^io} zJE>e^#f;#G9qNyORfrxL#2~XRWl{y}lPY?8H}dg+K`L0b3C;m2>x2zP9MdvE%1LjjSR z^$uW9D-|~h+#$_4U{ZBhH<;TJzKDSn=JJTL@><9d?*J$xx2~aZ(q62Mvdpu(#7e6h zKWR-0(=u3yLo$h3Yom%qJ`~hrQ5xEp%~6u=x7+=ZR0G!6XM1SLbF_lz$UvFnelDL{ z|4pZhE=NB_8_p7bUlvn>9!`bm)w65sI{Dwzyac9JL*qgd&nrXO%PW?1Nrcq5&`3nM zLoY1rE}m1b-_JN%oteA&E6`1sR7LD;B_;LZrA7LN%@R;eXkc>-D znM5@?`om-`^>4x~5M2wR#iYq{ z&|B`@g%-VVGZ?Y=#4xj(_9zB;z)6vGa#kf*;+b7ksD^QpGHGhk&461K>zmu;Y((%# zfEWBLIHG088Xo2aR17Xe)9%L2Na+GZBi zA<+YyXSs|&#-5`~@y0-?K<%AVGsB9@EoX|@7K`XNa%OECg~nA})b`{70P;De)H<hf+iR9u55WfNP^5Nt1NF{72dxHE|XGhf+ub`-XZ7Iy`MMnBaL0wcqvs9H8GZ*o`SQ#o5h;u6|9Hk3RO)>WH5~pbz$k{!){CU zqKz!Dxo5YUZS^WOiL`k-E{@J{&Nb(c>sRCx2@nmGow+x-``;C1A9Ez&Ho5@^g(zN&#LTB4< zFz_!@-n)#h7yaD^=0o&VL+7gMkKlE;Fd3#Lwj+zmAP8kWApR(f4#97~p6&+SQ$ zw%NJ)HwYZz1uy}9rGSMCZMpHJXljf!i04{ZQ~T}YZ0Ki2-V&sJ=ezP>y6tsNkHycI z-woM%qXvh6S**X*KAmd^r^=_gzqM*II(t!qNIMw~5y}wSLmbPJUCQz_Aur*WaD|Mz zI~Y^!#dGZyK#1$DZ&(iDUidwistB@UrJKLy8qj7I!41aY6~g>F&ZUbjM~NdY-rj|) zqqGSIEQ7gPI)Rg;Esr6CmI(UI6IFAdcNufSqMv{Bp+XW5LWU>rOW=WyxENt>Hx=%0xrCj$#V6U)#vP?m!AN@a z5`rn#3OwFy*2O_Kh2@7CahYrTcO-`#jKa(KG{f2z*Cop)S4k|UXX?K&^XziGpVXZt zBjK@ZIaWX!AJJAEoXtom#!tc#Je(a_EJ}q5CQ-P>)9kz3oPiV&O=xL1>8x1|IR2pY z$X%o1V`uXm(g7oj270unFL@#jcbvBN7Q&pd^$TJadU5pi^3&nNf`gO?5kQqZ-2rQGKrHIvMXjHZz$;Gh@XfVsxsFSLdJJ~ z;>RR3akuA9ES1SF!~u5mqNT%cUqi-e?YWc=y#`Y57(Awdo!K9uzu14Zs|f9{b;jl> z)h<7D#fs{k9L&uIOA)V3AhJI-O`h#WG9oy{_I3KPFe zw9PK6tJ{?Xdn(Wt-W&{fylN3K3Uo^!JUv_q%Dlviw=)2dp}0(fUT9xQ!c`(odb9S6 zV^SIfOYqJ~RkWQOVDiJQe3ZXwl&hDXPA}B8hKWJeAOaadZI>;TDG)!DN`BIFr1ME< z$A1cqmh^4uNnT_@AHO8C*7P97Q3L7%(zDPgB9v4iE@s%;!+4wp0#*H;((ro4G0=Zr zGR>xU={70zeR>k8Va*9uwyMxR!%fj{NR_gJeJ^H5G$ILK_57Y*E}~aw0e`Rd4a;@P z-$YxiWe#rP54eu2w5coqtT-U4{8MrRdUJ#8eMJebL(7HuXO3&WOVo&W7sMqqBwUYgl zgk5bp&;Q0TOwrpUQy7#;HKJ<5Ual~7ou7<6hlPpr$Z11nL7gABgx1LA z<2HC@=rc`dq_9!vdbwW|*UEh}Ii~pu%@nhW>Qa`YxDqpQg0t0HsNnfH%fvQ$WK1|#sh6+pK8uDx_@KZ1iC!XY{DHglH#4{kb&%%bq?jh!7lpn zu{N(83!5wHu$1ejw`dxQGzkn7Z5jE#W;l-fN!HrFib)`m-KnFHuIm)R6Z-JGy^?^z zdyfA=<@c)s%XkzK;#+PC?PXbxbM5DC2(soF?i}s*@b3y~qND%i@R%c8@as{qG ze2Xhn5*@dQ%d*Q1dDiaNsJAyaDh2!)$;U5P__C*w=@O=ZWEvzX#o4$>*wwxKT$|}_ z4W3g>>kaDteT)?Kl$*5EyuIq<@^i7;pb)GiWSO=P2)sxfG+e49?01x?_?REwa4X-q z8>Jv>E+GoTvC*yn1K~tnwj{xm6jgr&Y@a)Kcd$0J zcM~H$YX!Mz*kD`bI4oU?kYI|we0PKVRlufe4Pv?0LJBE0>aRJM!FXMqsL)v$=Im7p z2ns4o*-W|U!6E*&fl2lIAO__GZ5yNBCgL`q?Ww_<)ahWM3 zo|_$A1o^--FXrA+B$!((*Y-&2q~<}147r!M!n}H{tKsc>N?alcp9O}P!^imYnY_Tu zSz6Btx+ugW&A7|o!)lgMIgryB#pmXAt$jTX=xP%}vg@}Y**S{RebVjgqb-%!9o0RA z8`}#~Z+(4KZWPaay4#snC|^1>zYulSIJ}EOUGc=t2HG=nB{#{|%$rm-$u8qq+JhNL{x zMJV;k+xQdQVL{Z1l#qUx6a0T7x3TTLzT!k@Ua;Tsso+{6p!zwdO`&MgIZ}MmDp~#Q zQVAaBnGRG4b7GPMqS2*$Jrl2H;fe{gjc@wtnQVsy+Tvl^6PuS{O6`!8`SgkUO5%t$ zOuCLP;-6T7*sdGQZ_Vxg&`^E(n(xTGzAOll;~3Uka-~4SBe8G7szvHWF9euQWMhB> zZ1O+<7jT624RuE3W6lMaBVHw$9&eSFWo5-B{sGcg!46*&Rqx>e%(xl&M4kJC5~W=m zRSg5O%|Z8;XTTsbW(z>t#AxuyIipFXp_$%Jd7RY(XHPHO$w3xDHcKT{wn!@cW~MIe85r{uGm;OWKM=|5g~(v^KLOr&^wdZp;22<1Xo-J1 z!!+zX4SRy1G$yDLNJ7vznnFhWRSKW(2wr@t$oBeVd}@8UQ(Wj4{aRRKwzg1@em)cl z)TC~^j6Jpy+50adP*oKt>qauC(H#++0n{q})EK-!72uof}9YwEo zRTjVC32pG7MD2TA{p`J-bo!I?k$bR99I~Gs)R8iiTW1v|CDA8M&NeCNA$7BS#sT82 zj*-)Mwo^`&dH2y!qecu*!u(QwV1Wn3w;*V&yNDi&R706^PJ0xaaM_|v-wfYC;teK{ z)6yjYS2?PxP{80{e`c;mRS*FC4C)Apo7W>2A_QopVtPE7LSthiLWcqz9KK>s`u4@7 z>8Y9JCFVmq?0QUb5rhAM*3-FD{oYhFpZo{flY~>2q!f)igQp40L%y4$nrPUkX@hGE2MI6=W zR(ZzF2z(<|nox$L_k#H6TPK%e%E=GbHkbvAzJV;zsk3!T8$*R|3PQ_5Y89x5W0`5? z@T9rG+6NLf!vW=%PO(L$_C)9*yPY2mz&6%GHNWZPjw*f`^A9AU3V0e`U1$t}%YR^~Uo-+!!6c$lP$7WiXBrH@4kgNv1%q6xQjUHgS^zvS7Nq zG=t87&Hk@!{L$Vme{DYCLjpW=<7&>z4U^oO8{_Epr!`=Q<3B(@e9QtuRdCZLW41>< z;lGeYS4bpJOD;&eXm;|~dis6EZxnUtyh%z_s_;7N0^hdb0XA#T0W zRfyqV@P!Y1Te^3|$6|S=B~W3!3EVCqG{e%D+{oJ<7~}!S<)v;TZs#ZrJ>IN4cJjf} z^gJ>Zc%Ji45WqZj)F&&RljVYvd`tGK1jES9umF^n1v?%`uzlGs{ePeo={Tnp4?~5~ z5-xBLTq(xpfVZ0$z*+=oy9%D5Qilq2m&Hqz67@Rha%i1w;6TOfLG;Ki>k zhpo*>NuHQw|Hk9*U7QR-=rB5EUagtrUV=LUE#b_oyz!d_&O6y45N_mtT4qNRda4(V zKZ!lAiS-D$!WL^1&&Fkhwg0Q_#bCV1ZNXvY^fc)05fz-p{yo!Qe4BMD?D^k_Vf8lo9gZ0yvGlbM6TzUB)e=(VGRF>QtP+1YbM*#9105=u;J~5=slmm zg^t4R3G^9~Z_Q>$B1&xI7o`ES3F@xB@VgcU=-`cS3Quo!P0&ci22GE~ zlpaJ-i(HY2#+&P<#*DlvHf_@5>@nzrSCY5lZ=@aJvh|qm!xcJF@t-@{{rS>>~*2gO;+k9m| z3dPy`><4XMzOcqz=o-cDF?)s!r^$mCa%DuF!hVXgd@SO!P52FYuAak=2#G>XRTLBL zG&Jn)V?p9^fTGa`MwMhx=I{qIZ+@g5x3!e*DyDPnXv$A8w_#AsoVpBJ=@<7|KJ-A& zM=5w&MD5T*A`}y*~ ztv<&h<_RJ-hjV3W&-X)6jcngsm#QY5_i5>74bpqmwiTFYSq_5>I&ePZ#3v7RqR=B zHOPtW$)bWgr}FyA>ApKJpcmV7e?9uhl*2hOY}wf#^Ov1UXOP%H1Qq=%mkR4uukJ#d zRh(xKXg|RGQd_$nmHI(54G^gR*Ay9rZ*T7nd51*`aX_T8P-kZVRBCRBH*d7OFiY{DQ9z0bOWt5~1rA7fXD|#x&5(jC+rWb34OP zfMS7{yY#*7SlKdZW-ztY-1*;-TWm~S=5|K^#;ocq^^(&$yb~j8k){tD)S?!s+3$Da zewk_2ok+Fhl8tbi%`%F2u0L{l3~|9BOM#`!6HhPbnLU)ce8{#G$tzQD_q( znKGA&FUM3CxhJ?s&o-EF&Bs1s=#va9tzbyR^67kNdL&Srzph+HY zd&4er{W2xt*@QzN5rzs!8>)H|PbY^Zg72_Uet}$N2=Q>%l}3$Vp{w+(W_3AV+Z<}) z7sX{vCsIqnO}qJsEQNBrI&O7~$?>IEv-~m4=)5KL1yI$=xdb}9n{;6ea&Lk32bo-O z+JB(*fQ}%<=O>PvIFVsifN4slkP{c@FN2k?eRe!DyB}}w1xn!eenDh0@q=U~5uZ%O zTP&QV=Z3c8KhW&8hBn4_%FhlD5k}&Od8mjV0WU04ks-b5L|}V!t@6MEA$T&9u)|}Z zlh!6twzY4DjSv)jzbtjut}#b)5mgg5YW)j`?$9^~uLFYWVQfqamdljcA&W~}M$jxd zpRZLq?6O1;_`DJkEldPoK29ws3@6lRG+PFHIojxEy^19oSStj9T<81MMGSQ^bCZ{H zauPaQ;jF0YwS^W$^O4_}MeO~A8?VHJC#*A-f0Q{79w3c4V-@%7EixsT4VpqaOYCrKa$C0^e`ZjV}N zd62D80rP`I!qe);6iQUhx9r19Hzt}yjJ3#QJC!fyB@%Z@*3!dZM@;AaJS2*S)m9;h zz;Z7JN~@qqAQ%H*3W_^G8?~kIR~56oQFkT|BgqWmkw??cecZ8hD|UZ2J`EY9g&WL> zh@1%TlQ`j=tIAxJAmLqLAV?sgdMMfhVUSgxw}5z8#$3`X6X# zvr-#@&V$s$&{@Vlc+Z<$8x+K_U%|l5d^(Woj<7&R!IN`OMjJ~xO<(02l_%EYp(0;lFMFC5qW;}3MPGurIB|IQK5_pzAOr;J@s<6zvl@Vc zv=TEU9GKhD_wfFFo1c5rxSOX0CaUk5yyXL?q-b6W4`U2jhEQb`=6NP3 zYE{P{aeica`D4=APH~SE{4Sa}r&=vvEZg)!HJ0AZldZT1->}HWE6Xa8XmIHc4F!b) zv_-TwONx3T*m^^~bNm!yeTV8I*g4{4>ex{vT0i85Y&|z5OAEA*3XQp`<~O?(XhTLP5d-1O|`>>28qjkQhQ@Xq7JM zZlt@r#Q*vJUOdmfE_gZD*=L_!YpwfpQ*dhGrJdV)uZQxONWrA*^gkTba^kL^P;Ws$ zS;!iJ&{A)e^J5K2f0z){@Uzw(-!_Rx`FN);|8AEL7bX@+Uk!km%8A-Hb#%Qf+iBBV zRoI(ajr7+4Wc=LT7#}fCLJ3bHavp+sto>MnBfCZME!!+0Ca_G>vLtznJ(Dvge zt)DxzSjLcIH+C*T^1o3gMRsb5>lB~4 z3qAASSt=;;8GSz7RXc*#*Y+q3iXy(XEOzpdITUKn=?SQv7CBxcbAVc$_LrZgPitF^(;1B$TZAouD;=*oli*=i6Rqo9hvcr? zd#-?{W!4T7dVLe)=hTY)g&%n)1$HV{%6}+a4tG9&4LiJ-I(N+ytGi(mIf7_Qi%fK@OAlhmWn)y#1aa3GWOY^+?5=C~tit z0oFOu`WUGX$j#tt1zsn#g)(}=&faqrV*b5!)hr{S`Jl(S+`zo34@|L}g32IMgWuL^ zxu$b|>~QQ1t{G4uY)a6!h!bw%()LrH+yX?gUX*5wFyu${w zYOxYuVrY?J_3Xs)as}RGWQL)@P`!ZutaT5-Hn8mBh?z(T)x@V- zofI>_?6^)@@uZv{8sT}-?)_E0dS$|ed@s9h!Cn6sGgz72S6VWp0r!-gX)}WWldhS) zi(I8By18d((BvG0Usur_@F2?XEpQ0<89LYhMY$3ehDP^9?D#XrTh)XUvqmrOvkrxO zR!#H;^hdHPCf!D;EnpT)<%-Mwdmck>?)Fq2PD|LL8^{Lh%8*ufijGBM%jdLwwjZ>eM%v<7rs*)5?^l%-O# zh!w2RBS_j~BYT5|(PiK{F=HFI6f{>POu{*p7aVdvsvF2ejAY4&8 zM==J$m78P#TxWb5J#D2Zi`&a7#OI!@sPQr!Eh?5%Yyik-<0OJ~_iTmge1USfBmhk1 zRg=q;{kJG7h$0jCKV=*DpwP7tH>W4^AyG}BLDS1FV+}4%wGbgYHZz${-wO9uU-0fM z&aUgEvU?A~ld~pz4Q&+@vdiGQU7GA>W5u$utD}f5i1d(S4lx-{M@tD$wf!m>h2>bs zu^$@v$9a5Yx14%Jv#UKnrMjFRKcp*D$%sj6A1)N??^6v>Ga<{j-Uj|Q+=FzFnk@Si$fEp8`*SjY z70J>vmhfpZsCzxL1&;koBM~S>MyMyOj$g4QXTE|ROEEPF`{RzF|*^^qUm_Ri0??^ql>d>i~a*&)u|*n-(s#X z?#Fwr+2YM3{0YjZ!oQ?((E_A+n*>Xx@jPTrr|6{tzH;_r6j*0Mxsh=Ugf{O4A-FjZxo%1nO9SSoM+@+pfqP=y+OBNdk7F6Thfw zca)HO^c3?4LqStJfdb|zr)w6PdUDYbOhm&94++}6Na^T)V^Z~sM~6*A=&u*TBEJJ> z1Aca{4Dk~L3Ul@*_<7jYUV zgSqD>_IS`S*7in)-Q>ta{E8d(MCp7T8iWb3;V?*KyuyWUQTN8}mn~WcEdTtr{CBEx z7Ve0g*AjH_q*=*GVviSbwSM#E9O@#Ep?RUE5&rd%jweOw;CVaZ@0YDSmg8u<6RgH*D<8 zNba*^sH66^1O%+LTpIb!cTSAIwajp76@L(3kekT5UzT5Y^Bkzf(sQDgSs9){77FBj z{gRwVb>ntSqyCYPGzDE*<5@0DdF4aWs*31#()hnSnSV-RPt+KqpJxxl+T8TT-`Ow~5E zc4;Ks5K(HK7Kmw=y~y2kjvdip7w7vk%`W~(kIPxmNJVDBa8b}z@vd4+;RO+e5J((s zFBO=5vW`K`R{Y^6QGI}IU!Zg@V`_%Br?x@sgNFuIhj0jGX^u3G{PgF`VP+(kL}&wN zN`}IPjULY>vZiU+t9B$*j?<+vOF-(aj78j5Un`;;W(sfte83y%k|4-`nG? zWD7^i)&+wAI9~M$^{|7qP3{u%{=3ahso&?7L;jE+QN{w%=Xjc;iBH$`D}Edu(RF@Y z)JMYt2f8T39r6zix47o$ME48tI9E59>d{Fo8q7TZGQwu!8r7H053$neT8pziH_yy& zo|i7d7FPpN&Q^tCBS!TGddQCw z%4WjIh_V1arVuMjRF>fF67qs;E~U#%#{=qgI+)ED! zt-Gt_(jxr~TC@Rq@TQicmXV2{OX{8e3ns#?(7PoGzSC?bKhj>Wg^$CC9>;|$pwNVktcXs`0<-##B*9$ENidA3)SOZp#1 zd`k}|+R!8SRccAOk^a+Z);jF`zl(8RQB?HYTa=LQd(=1cDRsQRh8)*>p z5#*O-&f2t~m9F2u&OeZlB=J&$Jop>rM%gbOf#yN^Sb2*pkB-M4INtrY%8%-IZpY)% zW25pvkl;TM{MhDK_xqtx*)DBqbP_-dW7~Fz-vb9mj|NB^rMHpf4KE8`d|e_~5+(t_ zHbnnGTYdww^VC>n&2*)<*p0~1n{K9S#n7?`?68}6R=T+-SCX5zURa`dr)=d7KDv4~ z5fS0E0ftlBnwo5y8LRn298|z(MRp!1bMX%}web(M;NH#lr(?bF5LctWlIkH~RRTB+ z=M-coPDr-0OFy^u>;J;yFg&mK=RPZUh{$X80!71C*3ny#y%}0#s3XiaJ5g{$VsFv zW!Ou^X&GQ3+P0a07>7PJ#g8j(u>JqK1=f1ny-miWkjZoBdiaSJ(xG25pWKze0@btP zDkkm>kVgGpT9BjrJAP|DCyyK88&Sn~(|&1YKeSRzC-mz4GgG650P=1B%mh2R})n6C$_I z*(FLc>x*&m(eUFx5XZ29#9CNVr=z8ZLx{!%-_Kjp+z43?X(uDaCkokqQ`jfwQkolR zW+G*X;hGL0WAa@&C6RbV4JY1yONUN6OMni`5y4|C*xD} z2UZ-9Omwl$rHa2!1Ogf}Mxm0g=UHA|J)-=wggzCHJgBg0wA8$oxXPkUi3%xim2yc> zay2S#MBV~(jyhYg5$OS7oRph5p;(H<{81&ACc*r6%30SE=^Nuc`gb%zmO+JuLViBi16vw?gcI304&Qo>#6d0?9%(uI|uwfYLZ&F4mNGK#^-Q0dXL3bk~2ju*+7Oy z)xKDaU0a;UD*L@nnxabL1A`IamZzzq_~nVt>F0jOd=K)!RK8w80?lOuG3lzB8>2lI z7Xt%kBT8CzdlvMmpDT$@YZ@xaoV<;7tfP_rTU6X+>F-Xw_CbqHkSRW6b2k6wg0ZSS zS&6vtz;O>z%%l_hgQR-B#5g(E`!Q&}mMa30d!O;gO!o?5B#h28ZDOy^?5%W-aDw&L zMlz%8o*SvppujM$zKLawU)&uhYXP|gGKA;$xt1bh2e>%yMiI{v->aB3YUt55x*nPm zM)ON-#hvNzCH)mm*jCeN*;jYIV;d(g#TzyYj~A;=g{9Vk<@s)90S}Ee~Q3P77Ma9Ap+D+<)nT6N#yFZ2Z zj@kA}pUaFBXXA1;9f%VBa3#KZ8+?u<7kAwA)Vg%yWxi16c|Kc21zQmQ;azjb5|Z`@ z*9w|cD=*3T4f@(DFCA7SS&9#2j1;x?9qOQd%0}S4AGzCiwjzO(CEkmj@{}U9L+d@LgjFUU^xYkzSwq$hoJT@=1QcPl3 zG&!ys@$pEmQ^+4!x1IHe=gQ5uSgFUbgO>}tyw&y!%}5l!ttwHwtAJ@pN5_*~E9$I>`9u zLEeEGtTD)C7k9$8a*T%m@yQ)HVV9Yj>B|nJvPtoDf7$1a`^z?BZLInNtC_C3s9=wk5jJYM z*rW4n+CdoN+BO|&-p)i!AX;B&b?Emxg|+wyq8HPuQYiY`8$VpXu%Il|DPdo7lmWLd zKaokPa~34MimVFpk!ub-yb0c2nuu(um|-WD`Gr;4Y{V<4DjHCq*^RJ$vl`7EbbtR+ z3O%_{QJG;Ha@I2ZWKb5nECr?TubyV_q6fl^rR1_Wv6dSbOaB>F>z$J8i$1Hzg|0w; z{R1(8Ll0(hzD(vve_`f;4(ism(Kv|27A88rS61@GXxxs?|E^5gO?e;$#&NTNZDMQ* zFzF8OEZ;d)`wobOOLS(&FX7Hj@I+mQjFayRm**#zJ51pL>j4tyY3oq9X48%WIb|4o zJzPb7@R$;k>5$&2K7bn9*l-zdA6Syj7>MELJ? zYek#CQuNc}l4CSzsjjw@(D$uk~JhToGA^+MeJHtWP%m&WhO^< z7VjJkjuh60NHmU0-*`8&-)G2>d3E+5^9#4C!?#HzoUA?_N`^xm_DJ8M`sz>}8j&nM zA0&BT@XvsQahHWLhDHR2AaSqodti4BYYsxe6B%%6+^$6PU0eH(IbZ%MMdY+16F7`J zv6SXx`KQ7f#*C+p_B@BzyZz{ILL|ZqQ;DeJxC4_O1PKrO6~i^2h6Xy0)SiZwdydqf zh8k`M4zXV!{w0!X;=IrX?A&oJWEI^`R~P`&m(-M=MA5aPfg~RDu)e7MI`_Js{k%id z(|y^<>JlXNYRZ*(^Dse^U0D^_kjMo-c;B^jbu>97^Hz`$@Y-O}+_zJs5%WxG=}%zp zt!#9BgOv{2^juWmY+TG>H1)0|(0pr6=9~}NX)4XEqg14oU_7*IB z)10$*0MbpFA)}~rJ*nU|azvwF*7NlgG;TI-M#5m3xMS@!`&Lkq`b8_KbnRKVIdIr# z6lG*ToY0#f(=)N?(QbcP5|b6WropZ}IzHKdtp0xfR=r`A{lV`v5ZH_Gm`Ho^UVGS< z*cVXU9;}saB6U?uvj1K$mG@LSR-8)srCRJyD~X#?oi9z)eEynUyOmmi8Zy)$_BSMJp*}!Hgt0@r`Lt6i%iKn|KbCOt-n^D@>xV>Ln z8S`l!$iG@q`^`&j)*Y}ZN1AVx{N!c5dKb)eLH;06XkH``$6}_IhIw4u1jRb^IcwCbSxkffydJE}PG5HyMc!>R z)4Qc9av@#ktiqqj=qs!Pj8q62g=tPm@=6wq>@+@!G7r9)F1G2K!{;*cIlrj{OH5HR z3#T zq$Q0v@@A9-a~GO^>ZzQwIS^f5DKjVR#i)&B?Royq`TN)AMf&WNz>@ip39sI+c3*du zgc*IyDI2cwB(4i>E`EPJH2pD60o%fdg)+;vM)TDvEfZHKBlVpE+!xL5Y;G8Gz2|7o z)X4MJLc(n`28Y)?qwzM=qr9J;h!DGvq2Pj+h@; zAATAC^G9cXQb84m5SGY_k77M{Kh*tmC3hmK5$85l{#Mtp_Q1GiEgs}GYT7~Y1aQ#^ zt>w&lMg+kdP=~|Op?C$w;m9ySu7Y@_B^Llye$SkWB*GGk({1YjiloQ3GlMGpLNcu7 zI(G;_T0M-G1#eLZ(WcBdR`lhi%y1oH2pGqV@IfL_EY`hB!b`;vY8Z!dzQj@hmn)$J z>(bg`X+zYxU_+Vs5)vxpi%@$rQjoFpZ3&d*^SV&ZMmq->%cm+~ZYk9#@CAJ-2c4jz z#*H)eRiQa|`{IuI)eJ^?8ASCxkr1EBv5-HOUJO#|%!%{*$rKJQa>0)2?nD`GbgaW2 z7NiQ+iJm)gwpoCQ_ZLKh!9^XRwX9)iaRF*>lhRPvU?&`vZu2xHNu2MSE;k6_-~|4q zPFu=g6Ao52v#dEcjY+?+xe0o@5#-olz9pu_&^%Wl$Ew7ZV<-nqA^40Godi#xJwNwR zLK{IgIx_BcFqxeC(g^*}HhHj|3zu*(VYA<VTK0(s^U~_sL58WHItY@!!L#QTs z#3%TMS6pHDaBaGkiez~Q9g~4@OrVgmt7x~p&=`?QS$LI4|`^k>#A|2bw2UJ zqQCgKK95APVT+AS-~4EY{~Y;=3i|SH>uQD{{MOUfJlLgKofPDtdzA|~?%<=`eJK!c z%384iYcegVi`mD;Zl^`poQ+6g)tmz5@=&K49%*p#OBd>a#Y`nheRW>Th^iNazS6ix zAChbu$?eTF{ZNtoZ+(m*$jJ{G4uO;z3aQ1xiw#Rg-cuS}dd_w+mHp@ET1wEdjLnGv zseOpE7yPLX4%gB*Ih2I>xYH1+Fe#mPJ8MtX%t7&AmW-eFAJw=?&Nx8sD=X?h6jRtg6=6bT##W^;8H@v*pl1?O{Nv#COk(TA^OG0~#WK zb(ozL6qUkXp2!So>}3>TE&Oiye5us)lLNh|S-y3*y(8USAX*Jy^qOz}PV-w(3Z(iB zJ5a`aUbuR$AT}?cLtlPEX3Z*ZhhvX*#urNW?va%7ivs2v<&%5n{ zcmmA+;_M?qX^RO#jHH=kG5l%RegX8&A*KLpEMUBi8k*m%kM{0$!gfBw=i`U>j?<`^ zU2kE+Wvm%no`pgmgo%ImLZJdGi4>lTANi}O)^~TShs;xy9=heg|RGSO)6F&gHbEn!It(h#nix=NGR*~vt(wI(E z?kHc@+8u=|c}?j2=dQB-jj-dae$t0Zipv!QmZr&5W;vrCC@AcZsjNlCj$aJU5l-<# zXA;tC(LOEvUl>Vb$*T39HHao&ZCeQ8)to`{DZ#Spx<$vTmkk{)G&!DgK1dw}ALtjC5#f2d_@pmzcL!I-oCnT@Vv2fzd1y zk6bQ_LuV*;Bsd{vS32StJ=pH88polc{p1A`B4 zu$&0nAag>IsIt)*0&Iu_!_kmWb@3nvqSEBgDvU~3rUn$)c?G^|1?ycrp7>?j3Nzf5 zuxivU&u$QM%gGL1P29Z_M}|IH=+4*mEAfPY(9${_^+2(A&+79idDEYEIOg$`3VqCo z80y#GF>I>nD9Ohu7 zJ{|oAw`-5s^@4=&F;(P&uS;!lly$J^e^yBirmd4kBDmDdJloeNm(mU$0#+tkB#X_x zAtpG+GtjR*Ejg-`he;t%ke{-=CSwZ9RZ@9cZ1(}vzK${9Nnh?=I$AUoI#CVO*K#c= zrww) zTUTmEC^yv1X`_M@ldaae)RN=OIVJ!os-q10E`H0q?#Df?U`o|zQ@k-COZ&eHUA`%F z{-j0$K!pKx+b)ctvck5)UmeT7>JySfIcB8T>2Q7VjG_}WDx{bN6YioQ*y`iSLHK@B z4&~?>rn*Eo9_7M&`LOy4?ncRo!kDJm(=q@uA5gSh@-jb7$iy;Z6ZZ-gcqco(vp59U zi2=dOML|c#{7##0VfDO91fQ;r|K7|C)S_ipZiMwfkY|)P3JTBqHLLtB4{j|^H74+# z>c}-8j!mgdvP(Y2eTWSd*om~Gz(DiJmMhR^`Faz@hN$j+M76Uj`wGHpZg+W#QXJ#S zk#H2wO*s?5R-EHt*4uAMzOf3?dN1}HW$t-q?qbLQeT2GTPqj9ep zw4^MmY$%~teYB)u!L7FL&)%noZhn7eE|x5cV?j2w?xVS_^yTb~Ba2A{54dWxwa>kSHC&vDH_1^S` ziqbRSc-gNo! zsr&=-iQSuQZXeG%*KwjBww*NN#ER64>+%DTR0>j#e}ESVd~rEfhI$l}u`F1&mLOCI zICLgKn1<%7NY2=2Fz*^MiPD#`EzPF72J{ZE%=>=+ z8RrhD#D(&s^UsiJiqbkp{UFw^kH&>!Z036MzU}yFasD`{H(5*lKN&z@7$0Gui`t~f zaCUa_2Onad2Bn2n7tD~p9=NPtO@jhy4qK$5_B45R5H*Kl!@-gFYki6^~WIM*P{yF<>sCTRTjiwcLG@h}fTh=HMOUul8yB%J^g5ts;(# z-l+gfmrM(@mafX&2B%Xs>Xxp;eMS&(pa15c{n%fa`x#gI>JdLv);IrKh~k%0PZ;fl zo|yt=>N;Ju)GL!tXV9;X_%QmHf_y(;WYBKA%{fR!rsS!Tiz#>oHn1}QJJqiYw<-Cw zAs1*eqbhU6cLzE8QYtxA(&X_x+~T$V`;uLxK_c_Osy`KZk@-!lYe}Kz z`mp)3Z%v(#t(^DNTgpYXmT82cY{D5*v`b7sA+-9Z()}c^zV3!VTltT6Q(0-Ovio(1 zS*fJVPQkXI=A0_!E=N^y*ZsF_HvErq#OY>!pimk`uRq3}bBd83I&BAEzJbZBr{b_rp$XB9@Akmq~!rg)ecl){Q z0n2CRPOD`bbgddkqC|HEyyI$byg7T?xSRC#9E!>pX<9w|n)0N{Fgdp}AbJV)ha7J5 z^6a~8;qJ#-PRC-CfG$=cPfH}HeN~#AgSjxPFRTvjOZhLwP2{_zJ%z5*cr)pBV0rC* ztl?2eY0e93ucXWCKTyEQ5c)#5Hrk=6EiPQslRBCjSzHyfAxb=;NIaXj2@OgfbB1{B z@82I+8)7z-k$g63NS+>tV~y%)Q1I#-Nf+WQ759nD1vE(h=6#9`lBpFHQGL$ih;qdO z!nk9z$!J#=K+&HbFcnl7yfiyqH;Bvs^o-oX(Ymm?=m5&`>|@p8C_)&)X3#_4MQMk< zkR>bsyD)ls`g7GGZ*8b??8Cg9AC8lRWQOgB*Va_|Q9k;7ok|@W3DEGhW<$04H5bfb zZIx^BF=arAzeX5Zer%vC0PAlMBaNxxQeyok^ab?`CV@;tK0;~Xnlu4!5I=qrv34jx zUwiD^0~=;xqQ0#srSq(t)Hu1{y^rxpg0oL2JtFIxBjr~jg>2a2yFNl-;ORDT#Sg&-|yqfU* zrhi!RiiFeuJz{9Zhs%RH`2 zo9+JKz!=@*!$y!IPbsVV(D$B^?wpUi5mN-S#_U`1($o$|)Y3YW4dfYPJe0}L4|$)N zz|nrnQg?xd&cp#`d(lHg*FO;2aQ4zM7_IqRY1V-{js34Hby2Hmp^x3pCi*o}pS#z zp8uwYfA$ib7e!Fc$M78Wt#witAkfe)v}MiqCcWAI;|L~AND+kuIrtEhoHgOqCt0^$ z%4lhM;bRKXZXtoMi47p*0c|%KN$8;kHVJtl9%u@WXa4c{!SE7}hu z%o^-+nMq~mm3-kv`gIb6z_Qq_f{jdx5CPk!;V`2mj4xf0VXA0B)!GK!b74%QU*xd@ zjLj;M)GcyQAkVvKY-ev@aA^tc{M?n{s{{>9`6UoL@g>o_+N>?mg6*Sg$cV5Y(WPDs zhOPx2E}+3aDJUMFI$fu9_v$0sRhD06JE9{7{8!su6put?%+$Mt1h@66aOfP~EJ_xa zAE2mgvq9YJee?WkTU@W>z}E>w^lwJYMe1{E zza)l#dF_=1u&jv6*-X1EU4(q};QHz*<4@Xtp4C7l)W>bl)^^7W%3L!on20q>qx%gM zQq8g+6F3Y*YaIs;M@65z;a4wa{6Io%M%ai4^mm|mg3E#% z^My-5aW3r3vFyyTyCiDpc0z%@oKF@HTWtO;x6-TYwe|3~!344f=pRU2i4!Eh1<%FZ zSS$g>m7k90-3=73GcT7Y_Ah;$1D###cGj~sbro5;m%hY8I$(ca6VE^pPqf9q*7)}~ z-dy5G#F4s4Q|;ZqjOk9G9@w6A3`~6xCu-%Pxd7?q&NoZ%r_k_7l-*<3H}kJ(KsGEt zySivvzrXymYB+yD!TtKe;+Lv*=@C#F~TQb1zYXu$+LZJ%OG5C8fJ4p6@9oW%*`);sy^ zxckoeklJEj@bfnm-*0h{#+A}?N?uLKvrc+L$^$T5e+thcxO#QfF${kf`Q_`UzCzzj z+98({%r^%RlQABJ_%(R>r8LB*QJ@cIE#`@^XBQU@vIYTDz)L zWs-4zuKklEO&3Y4VHpz$QaL3#S~K6HA8#D#_0Dkemzy ztc-m;ek&+u;J{&YjC@3KevM!z(9v?5dfulW#0=8ALd1A~{}Rq3qY@G%tOK0olQpu6qcF1T`dRrxe5LCoj!K4- zRr8iMkPm+%+?eO`@`rza3LU2Un3Yiqqc)uwj*68{Iar~<5@wmRgnNgg)k^qf1H}pXUWRWn zyESM^T02lNf31O{*p8@Pz238F8(IPD%wpK z_na+BsY6Sos;&vS>T0Zi^QovLVtoWA!DdfH*^3^#5q?vK#0g+5=*!gXugs>$(8-11 z_{j}(8+EvMA%z=B$gV@PVyj(LqEsf@SW)d&sgeRNWb_ztig1d^Olh}Uq@n{p=f9BI zODcv=C66Ipi(nTzd%p#q#J_{GOO_5U;Z3(CvwN({p!%TJ=jjEA?OsLkxwCc_K4SY3 zAib-nN5J*Rl4xi>ND4S;587sU6xaZUB`3OuQ+iZs(sr8b^hB#9oUxW3t*LLpHA$J9^Y{y8*?(pl9sC!E*IU%0WA^@a?QHcLgTH&!BWLh}a@$2%8vS}3Y>%#&cbj11UgjUD z)#jA_df4wT$-{@%Cx1oNSpdz0Ld9OBiP@K?(5T#chV=LlKB40`?3bi;_j=wn(b(oZ z7b;;Z4%%3_$^1)fgd4;I@xNc@m7i>wwSQEfq>U-)$YaMflsjJ3U6~0qk-{!iGf8P* zEUjTXGd$x_#P}Sm)KSiCc(K``uQ_l;6L8s!O9FW z@e^vaME;@%X*6}|&WfLSbES*Y%hgkLykY@{w1M{fpDUOI3dafKpGqlZ5oZZn)2e9q zY|*!(8R&qjII$s5dxki^kG3u>mqfdu$l_kfL>gKx;WK4q1k)Hg`2N=;qPeV==FoPX*^(2L_hx&|glHR1TD&WPG#kchz8@C@zou9awi;8%Osc=X4U zFD>DR98=A3^uFG?6oBX2S3e9cB5)S1|LmlvV05;fOQnxfbI{|*m0ucbyEa$oy5@-Zd@e+7-BaD6Vg6bZUF4)>Cvp|8^C*blzrE7dy9A;BT3Q35 zLED2ty5utg?Qa|gdFgF9*@WKn25pgomMiXZS5ZPvg<7~&@fZQEYw~v(hmb>a44gJ9 z5OJxN&{k9;Ab}{J;BaYR0LN{590$r&v-Rmu1C(zZA=Hv__(1qgRh*Kp8?~w@b+z_h z?HKxa!hL4AmZ7eiNSw{_FBX7E?j;H=mN<5a1uQ*FtK%zrEw)fO`_EymuWfm4D1rj! z9QXQG^h6l*j>KohE+i%jBL&yR8XUjjb~X-*xt38; zP2r@Ud`D*68dBDJT0*T3CZ_Fh$5ZS#)L1@mM58=Nz_OO;AE3N8LyiW!b8M91bxj0W zw!7IM18;AI;97b;ybgWu^;4+~r>(p>zPTccN%U0@b4;2~2- zFo&0lCOPSk*PKq=P+stT-Knp|M@U#H7N7{mFWp>4M#f@TF5Xs<+(;QrIrwjvRK)Vy zEiZwzwMVtwzA;u_>2WC%xfy{dC#I+>LvaB}WMpU-*OAbX*!Gv)cICZ=N9lR^K!-f< zf}16&y9C5@*e)czN#Bb+>;>y`_2E%_>1EW0TaPS`W@USEGeCac$fy-f0e?FFP?ZaH zq_PfL|295#0#$5V&&~H|HgESZ{YK%-NcO=nms03;;~vX{y4??HTkEW(Gu_tyz|0cX zHllZbNQ?Kp$gJ2EV*!uMs^13QGDO|Z@auef;mU>!fcxLDIP{-I0=})QJ+Di0(6Feg zw&0VCZ`p!F>giEy$2U=Rnl7|a&uT^GS;;y|NV7K9xk8s;>jx$-=pZ`EVJS8}p zcMHbhy!U45=xPK0?q`NLcFqg_v#uZX>Ug-_h0sm)wS#4?e%0u;hzQlVzg-$RgQIS} zU;w}ols$jsiPd5nU}s|gK>ia7vkfC;nCa$XCL=yF|IrDNfr2I9LapPk1HX;y^KG_d z&pMXsiKw@FKS$R5y`u%99>zZ9He zXQFcq#9Y%#^>YA4OjW-Dk{Ygo+&oT6KY$?>z|uQ3CBKnk9CofoR^Ai_2!Dbb% zZ{NNF=E+NvdSiF0L3X=_a$vKxM0L(+!Mtzq)34QXzsLzX^Tr7m1wjj!O+?a_D-8x|qq zU_F?MduDZtXd3I(y-IG~O$_zaFgv;1P~~Tb6Kvr^k5&`>(DGOM^>0ZllL6OtV4bnD zGOMUW{9XY5e)of|7}dD1vk0q_Z{-QyBOT((GjG1nLj!{pCgsCn%;{1nZ44D=t>HKh zo|<}}1ZRZrz_?;gE1jF$8Vmq?G#vd+G*OvJS3ObXy@uj$x?!!Xm5upH|1B}5M4%DO zlGFv|TxXX>`G> z8iW6y(aLg%yYM;cm-rF~Mc6s{_j*0$ho-m)_3^9P%mL2+O=1zY?9fenYAgO2ST)EC z(%%b6n@#y@zZ^Ar^}S~JZ5?J0;Q2ppJ(B(h{70eMo%kj8!8Z)yL^F;(oMdD4#`B5B z_WuIL0y+I_nnnlExs5r1RJuBu`LKB>Xl;{t<#SP{(kwVVuN~oiqOHBSeTZoo$7!x_ zh)u4ic25XR8T>=qt_rfUva+(WEmlAUV2NnVr%wxWa2{ys8e#a;w21sMz1~Ztu773_ zJXhFjr0q%?JCu4FA=`VOeC||KRPm0h0ss#~B&=Y&hzq5fPu;e|)_2h|nWGn-v)}ao z4HkkdMbV=TH;Kia*|*rf(KL8T8Mmz5N3lmdatpP<`a1;ZV|Hx?Nu%kXoMAgdQju#B z=D%YB*_FY5%&8epY>gFo%22+|q=({ZLLDc3b`zu0b~X)7k4flG(+Iz5DH7v-r!@mN)*R%V^ zIlfmObM^@asDj01p3HYY+NtMVpmTyyAhukU=V_L8@^k2Hw4Ofo0ELbC-_wQ zXkD8{`Q%sGo;wn_cN{{!Q+PGr7%#Q#_TzZ+zrixD2OJ&{a6DE20KnWQO2&*x3+=Gp zz~93kg6ZWWs}b|TY*jlqHNFDn3MLbGz3%Zidy^rJgR0O!hAxUXX3%_*qlbfS;B3Ck z;n|hL#}(1kjGoit5H%wx3&HKa4N7LF+HI#&WsxEA3;0U;dK5(*6-V`B@oi2*NLEvi|mXFndbqY7TON;6A8P sgW8eq^S8k^j-9`N9`lL~XvhKlJ+NCNlG$${wnreWIV%tU09%Lu*>_|Tp8x;= diff --git a/tests/e2e/node-js/index.js b/tests/e2e/node-js/index.js deleted file mode 100644 index 0e411d9..0000000 --- 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 e46bf8b..0000000 --- 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 75dcaea..0000000 --- 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 0000000..c978df1 --- /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 464df30..0000000 --- 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 1dbbc24..0000000 --- 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 0000000..37af5cd --- /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 883753e..0000000 --- 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 6f9defa..0000000 --- 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 0000000..7a5a43c --- /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 5699a3a..0000000 --- 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 6ca3079..0000000 --- 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 0000000..c319315 --- /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 61be733..0000000 --- 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 935c6f1..0000000 --- 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 0000000..80ccae1 --- /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 689e234..0000000 --- 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 4e3dd4a..0000000 --- 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 0000000..384ddac --- /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 0000000..3d7881a --- /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 0000000..849e070 --- /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 0000000..c550e29 --- /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 69e4cea..0136ffb 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 231c167..0000000 --- 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 7fa5d2c..0000000 --- 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 dc2f3d8..0000000 --- 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 aa1bf85..0000000 --- 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 96c359e..0000000 --- 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 5b89e51..0000000 --- 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 96e2b15..0000000 --- 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 2402882..0000000 --- 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 f033c13..1935915 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/54] 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 0000000..a2da4dd --- /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 0000000..67dcd73 --- /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 67507d3..c328b0a 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 ceb9760..b792052 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 3282e61..cbefce3 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 0000000..6b43775 --- /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 7eaf631..ddb4b7d 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 a672615..d1fba8c 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 09d365b..f7b04a2 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 0000000..b190980 --- /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 55a1a52..db692bc 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/54] 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 a2da4dd..678e029 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 c328b0a..57157ab 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/54] 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 870fe3a..3f5e57c 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 7cc13dd..3548c5a 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 57157ab..9af5467 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 c1a01a6..68d1b0b 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 d1fba8c..0000000 --- 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 2eede58..0000000 --- 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 6c2868f..0000000 --- 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 f7b04a2..0000000 --- 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 2c75a6c..0000000 --- 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 15ce7f5..0000000 --- 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 02e7e89..0000000 --- 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 865c392..0000000 --- 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 e0e2d2e..0000000 --- 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 f84053c..0000000 --- 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 47d60e0..0000000 --- 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 1aa9a40..0000000 --- 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 d5162bf..0000000 --- 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 c11185b..0000000 --- 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 c450e4b..0000000 --- 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 2100cf5..0000000 --- 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 d30dbdd..0000000 --- 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 d902a5b..0000000 --- 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 7e516de..0000000 --- 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 c189d4a..0000000 --- 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 d7c62e4..0000000 --- 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 a155869..0000000 --- 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 1effce0..0000000 --- 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 a665117..0000000 --- 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 cc9742b..0000000 --- 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 c323a3c..0000000 --- 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 98d9bba..0000000 --- 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 3d27514..0000000 --- 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 ec8140d..0000000 --- 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 2035c6a..0000000 --- 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 2cc85df..0000000 --- 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 acac59c..0000000 --- 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 1bccd5a..0000000 --- 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 4ef295b..0000000 --- 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 a55ee9a..0000000 --- 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 bb20821..0000000 --- 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 b9d2328..0000000 --- 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 ae7291f..0000000 --- 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 f6a051c..0000000 --- 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 b41409b..0000000 --- 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 f54f024..0000000 --- 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 d51184d..0000000 --- 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 d7002a5..0000000 --- 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 5cc7e0a..0000000 --- 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 d7e6959..0000000 --- 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 f6ce3f0..0000000 --- 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 b3758ad..0000000 --- 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 a4b07ec..0000000 --- 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 16d682c..0000000 --- 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 bcd87a7..0000000 --- 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 6479106..0000000 --- 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 5688f6a..0000000 --- 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 ecb444c..0000000 --- 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 94765bd..0000000 --- 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 177dfc8..0000000 --- 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 6a74898..0000000 --- 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 0182363..0000000 --- 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 ddd2fdd..0000000 --- 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 51a6af2..0000000 --- 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 0d03598..0000000 --- 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 3640cb2..0000000 --- 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 ba7083d..0000000 --- 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 8106d49..0000000 --- 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 d6272f6..0000000 --- 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 08963af..0000000 --- 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 a8a5b81..0000000 --- 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 cfff24a..0000000 --- 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 4facad5..0000000 --- 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 047e086..0000000 --- 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 e9f2d70..0000000 --- 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 ddbe007..0000000 --- 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 707a2de..0000000 --- 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 b190980..1ebd0bd 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 fe15966..470e580 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 4e5ac01..0000000 --- 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 50e93fe..0000000 --- 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 4d6634e..7c24f56 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/54] 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 9af5467..fa65ec9 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 cbefce3..4bd166c 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/54] 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 fa65ec9..caed6f5 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 15beb06..043e01a 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 9c8c3e2..0b020c2 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 eb7f8ff..ee190dc 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 1c7f693..5d038e0 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 770f5a1..2bc5c91 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/54] 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 caed6f5..66a8df1 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 2bc5c91..94e52cf 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/54] 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 0b020c2..0479782 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 0000000..0d7948a --- /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 ee190dc..dea7b1c 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 0000000..e96df48 --- /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/54] 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 0000000..c158234 --- /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 0d7948a..3539e3e 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 0000000..c812ad4 --- /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 0000000..bf50a5e --- /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 0000000..385d5fa --- /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/54] 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 e96df48..0000000 --- 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/54] 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 c158234..e4db32c 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 385d5fa..f43ca11 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/54] 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 c812ad4..3189a48 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 f43ca11..bf2c485 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/54] 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 3539e3e..a3a85de 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/54] 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 3189a48..08792ec 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/54] 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 08792ec..d464158 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 bf50a5e..44310fc 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 bf2c485..54a6ec8 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/54] 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 66a8df1..cb2a397 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 5d038e0..f50d739 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/54] 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 0000000..174b4b8 --- /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 e4db32c..17fdf74 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 a3a85de..6cd2121 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 54a6ec8..7d43bd7 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 0000000..ffc53bd --- /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/54] 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 6cd2121..4d48568 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 0000000..2b40e31 --- /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/54] 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 4bd166c..c9f1864 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/54] 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 c9f1864..65f81af 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/54] 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 cb2a397..6bb89db 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 043e01a..991d03e 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 0479782..d4ac7d3 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 dea7b1c..d9fd9d9 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 94e52cf..cf6c7c4 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/54] 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 0000000..4e94ada --- /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 17bc06b..36cdbde 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 4e6b5a5..dd09d24 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 0000000..4f2ca2e --- /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/54] 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 6bb89db..bd62087 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 36cdbde..bda12c3 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 dd09d24..940ce2b 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 cf6c7c4..d1db98c 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/54] 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 d464158..f22a1a1 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/54] 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 ffc53bd..38afeef 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/54] 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 bd62087..a4e72d0 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 65f81af..5f46d23 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 d4ac7d3..22bb5c0 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 37af5cd..d3799f7 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/54] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a4e72d0..93aeb61 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/54] 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 93aeb61..8599545 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 ddb4b7d..9d1c2f8 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 d1db98c..e27f66f 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 f3b1af6..0000000 --- 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 1935915..8311caf 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/54] 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 8599545..2863770 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 9d1c2f8..ddb4b7d 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 22bb5c0..bb1db01 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 e27f66f..a1a1acf 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 0000000..f3b1af6 --- /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 8311caf..1935915 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/54] 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 a1a1acf..6ff8f62 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/54] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 2863770..ad85869 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/54] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ad85869..5d25590 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/54] 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 6ff8f62..a1a1acf 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/54] 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 a1a1acf..6ff8f62 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/54] 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 5d25590..0818544 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 6ff8f62..cb63523 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/54] 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 0818544..52f982b 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 991d03e..8e3ad70 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 bb1db01..0e650a0 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 d9fd9d9..39d0cc8 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 cb63523..a95d745 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 f3b1af6..0d223f4 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/54] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 52f982b..cfcbd7f 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/54] 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 cfcbd7f..282d18e 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 5f46d23..046fe55 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 940ce2b..8f0bcf0 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 8969d90..515376c 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/54] 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 282d18e..7698a81 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 8f0bcf0..2ba228c 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 515376c..dbe91e5 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/54] 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 7698a81..d42605f 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 046fe55..5f46d23 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 2ba228c..940ce2b 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 dbe91e5..8969d90 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/54] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d42605f..cfcbd7f 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/54] 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 cfcbd7f..d458a07 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 940ce2b..f8d1621 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/54] 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 d458a07..cfcbd7f 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 f8d1621..940ce2b 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/54] 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 cfcbd7f..db7d8f1 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 940ce2b..797c1ad 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/54] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index db7d8f1..f8166f4 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/54] 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 f8166f4..cfcbd7f 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 797c1ad..940ce2b 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/54] 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 cfcbd7f..5908457 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 bda12c3..51e58e4 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 940ce2b..b9cfe79 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/54] 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 5908457..2dc65d2 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 8e3ad70..7f73cc9 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 0e650a0..232c415 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 51e58e4..60cc172 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 b9cfe79..844ed71 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 f50d739..6d73d69 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/54] 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 2dc65d2..335e38e 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 39730cf..f59a468 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 844ed71..057a5fd 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 6881e30..4aee2b5 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 8969d90..a11ad62 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/54] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 335e38e..e13d327 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/54] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e13d327..bbfdcb4 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/54] 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 bbfdcb4..b0086e9 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 7f73cc9..78b8a89 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 232c415..6a110c3 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 057a5fd..a2007b6 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 e194b45..0082557 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 39d0cc8..de6db99 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/54] 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 1d893fd..0870aeb 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 7cedd2afd02a822c3bda392ee46e033bbae03fcd 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:58:42 +0000 Subject: [PATCH 54/54] release: 7.0.0-alpha.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 76 +++++++++++++++++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 67dcd73..e5fc86c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "7.0.0-alpha.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..553b87b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog + +## 7.0.0-alpha.1 (2025-09-06) + +Full Changelog: [v0.0.1-alpha.0...v7.0.0-alpha.1](https://github.com/imagekit-developer/imagekit-nodejs/compare/v0.0.1-alpha.0...v7.0.0-alpha.1) + +### Features + +* add url signing and test cases ([b1594d8](https://github.com/imagekit-developer/imagekit-nodejs/commit/b1594d8e0e416811bb7a87e3d14492725dc1b2d4)) +* **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 ([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)) +* **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)) +* **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)) +* **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)) +* **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)) +* 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)) +* **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 + +* **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 ddb4b7d..0b3aab4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imagekit/nodejs", - "version": "0.0.1-alpha.0", + "version": "7.0.0-alpha.1", "description": "The official TypeScript library for the Image Kit API", "author": "Image Kit ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index db692bc..653ebcd 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-alpha.1'; // x-release-please-version