diff --git a/.github/workflows/build-sandbox-server-image.yml b/.github/workflows/build-sandbox-server-image.yml new file mode 100644 index 0000000000..cbee8a9e7b --- /dev/null +++ b/.github/workflows/build-sandbox-server-image.yml @@ -0,0 +1,132 @@ +name: Build Sandbox Server Image + +on: + workflow_dispatch: + inputs: + tag: + description: 'Image tag (e.g., v1.0.0)' + required: true + type: string + +jobs: + build-sandbox-server-images: + permissions: + packages: write + contents: read + attestations: write + id-token: write + strategy: + matrix: + archs: + - arch: amd64 + - arch: arm64 + runs-on: ubuntu-24.04-arm + runs-on: ${{ matrix.archs.runs-on || 'ubuntu-24.04' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-${{ matrix.archs.arch }}-sandbox-server-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ matrix.archs.arch }}-sandbox-server-buildx- + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Ali Hub + uses: docker/login-action@v3 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.ALI_HUB_USERNAME }} + password: ${{ secrets.ALI_HUB_PASSWORD }} + + - name: Build for ${{ matrix.archs.arch }} + id: build + uses: docker/build-push-action@v6 + with: + context: ./projects/sandbox_server + file: ./projects/sandbox_server/Dockerfile + platforms: linux/${{ matrix.archs.arch }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.description=FastGPT Sandbox Server image + outputs: type=image,"name=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server,${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox-server",push-by-digest=true,push=true + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests/fastgpt-sandbox-server + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/fastgpt-sandbox-server/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-fastgpt-sandbox-server-${{ github.sha }}-${{ matrix.archs.arch }} + path: ${{ runner.temp }}/digests/fastgpt-sandbox-server/* + if-no-files-found: error + retention-days: 1 + + release-sandbox-server-images: + permissions: + packages: write + contents: read + attestations: write + id-token: write + needs: build-sandbox-server-images + runs-on: ubuntu-24.04 + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Ali Hub + uses: docker/login-action@v3 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.ALI_HUB_USERNAME }} + password: ${{ secrets.ALI_HUB_PASSWORD }} + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-fastgpt-sandbox-server-${{ github.sha }}-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + TAGS=( + "ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server:${{ inputs.tag }}" + "ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server:latest" + "${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox-server:${{ inputs.tag }}" + "${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox-server:latest" + ) + for TAG in "${TAGS[@]}"; do + docker buildx imagetools create -t $TAG \ + $(printf 'ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server@sha256:%s ' *) + sleep 5 + done diff --git a/document/content/docs/upgrading/4-14/4147.mdx b/document/content/docs/upgrading/4-14/4147.mdx index 892dacfa0a..711315ce7c 100644 --- a/document/content/docs/upgrading/4-14/4147.mdx +++ b/document/content/docs/upgrading/4-14/4147.mdx @@ -6,6 +6,7 @@ description: 'FastGPT V4.14.7 更新说明' ## 🚀 新增内容 +1. 知识库搜索,支持指定 collectionIds 来进行筛选。 ## ⚙️ 优化 diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 3cd27e23ed..a539ec1879 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -120,11 +120,11 @@ "document/content/docs/upgrading/4-14/4140.mdx": "2025-11-06T15:43:00+08:00", "document/content/docs/upgrading/4-14/4141.mdx": "2025-12-31T09:54:29+08:00", "document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00", - "document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00", - "document/content/docs/upgrading/4-14/4144.mdx": "2025-12-16T14:56:04+08:00", + "document/content/docs/upgrading/4-14/4143.mdx": "2026-02-04T14:27:58+08:00", + "document/content/docs/upgrading/4-14/4144.mdx": "2026-02-04T14:27:58+08:00", "document/content/docs/upgrading/4-14/4145.mdx": "2026-01-18T23:59:15+08:00", "document/content/docs/upgrading/4-14/41451.mdx": "2026-01-20T11:53:27+08:00", - "document/content/docs/upgrading/4-14/4146.mdx": "2026-02-04T14:20:54+08:00", + "document/content/docs/upgrading/4-14/4146.mdx": "2026-02-04T14:27:58+08:00", "document/content/docs/upgrading/4-14/4147.mdx": "2026-02-02T18:48:25+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1251eac8a..b356fa85fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -967,28 +967,6 @@ importers: specifier: ^4.22.0 version: 4.22.0 - sdk/sandbox: - dependencies: - '@alibaba-group/opensandbox': - specifier: ^0.1.3 - version: 0.1.3 - devDependencies: - '@vitest/coverage-v8': - specifier: ^3.0.9 - version: 3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)) - husky: - specifier: ^9.1.7 - version: 9.1.7 - lint-staged: - specifier: ^16.2.7 - version: 16.2.7 - typescript: - specifier: ^5.1.3 - version: 5.9.3 - vitest: - specifier: ^3.0.9 - version: 3.1.1(@types/debug@4.1.12)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) - sdk/storage: dependencies: '@aws-sdk/client-s3': @@ -1031,10 +1009,6 @@ importers: packages: - '@alibaba-group/opensandbox@0.1.3': - resolution: {integrity: sha512-f0hq1Oot/lVFo0oTGJDRpkLxu+bqrK/Fr2apI6fr2hjJ0iHhjxIuk9g2tXsbrB00DfzyS6ULxLW5LxUExmCzIg==} - engines: {node: '>=20'} - '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -5531,10 +5505,6 @@ packages: resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} engines: {node: '>=12'} - ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} - engines: {node: '>=18'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -6085,10 +6055,6 @@ packages: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} @@ -6101,10 +6067,6 @@ packages: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-truncate@5.1.1: - resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} - engines: {node: '>=20'} - cli-welcome@2.2.3: resolution: {integrity: sha512-hxaOpahLk5PAYJj4tOcv8vaNMaBQHeMzeLQTAHq2EoGGTKVYV/MPCSlg5EEsKZ7y8WDGS2ScQtnITw02ZNukMQ==} @@ -6197,10 +6159,6 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -6958,10 +6916,6 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} - err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -7605,10 +7559,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} @@ -7948,11 +7898,6 @@ packages: engines: {node: '>=14'} hasBin: true - husky@9.1.7: - resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} - engines: {node: '>=18'} - hasBin: true - hyperdown@2.4.29: resolution: {integrity: sha512-vwpa65JOmo6zBdvmNV3tM5IxNMbTRCXmCz4rajM9NHuiI9aAMw9tGzp8FBO8NT7ZnyWND0HEY6vKCVYl//U8kA==} @@ -8179,10 +8124,6 @@ packages: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} - is-fullwidth-code-point@5.1.0: - resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} - engines: {node: '>=18'} - is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -8872,11 +8813,6 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} hasBin: true - lint-staged@16.2.7: - resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==} - engines: {node: '>=20.17'} - hasBin: true - listr2@6.6.1: resolution: {integrity: sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==} engines: {node: '>=16.0.0'} @@ -8886,10 +8822,6 @@ packages: enquirer: optional: true - listr2@9.0.5: - resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} - engines: {node: '>=20.0.0'} - loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -8973,10 +8905,6 @@ packages: resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} - logform@2.7.0: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} @@ -9394,10 +9322,6 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -9588,10 +9512,6 @@ packages: resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} engines: {node: '>=12.0.0'} - nano-spawn@2.0.0: - resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} - engines: {node: '>=20.17'} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -9849,10 +9769,6 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - openai@4.61.0: resolution: {integrity: sha512-xkygRBRLIUumxzKGb1ug05pWmJROQsHkGuj/N6Jiw2dj0dI19JvbFpErSZKmJ/DA+0IvpcugZqCAyk8iLpyM6Q==} hasBin: true @@ -9874,15 +9790,9 @@ packages: zod: optional: true - openapi-fetch@0.14.1: - resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==} - openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - openapi-typescript-helpers@0.0.15: - resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} - opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -10850,10 +10760,6 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - ret@0.4.3: resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==} engines: {node: '>=10'} @@ -11147,10 +11053,6 @@ packages: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} - slice-ansi@7.1.2: - resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} - engines: {node: '>=18'} - smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -11319,14 +11221,6 @@ packages: resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} engines: {node: '>=16'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.1.1: - resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} - engines: {node: '>=20'} - string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -12565,10 +12459,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -12753,11 +12643,6 @@ packages: snapshots: - '@alibaba-group/opensandbox@0.1.3': - dependencies: - openapi-fetch: 0.14.1 - undici: 7.18.2 - '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -18464,10 +18349,6 @@ snapshots: dependencies: type-fest: 1.4.0 - ansi-escapes@7.2.0: - dependencies: - environment: 1.1.0 - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -19108,10 +18989,6 @@ snapshots: dependencies: restore-cursor: 4.0.0 - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - cli-spinners@2.9.2: {} cli-table3@0.6.5: @@ -19125,11 +19002,6 @@ snapshots: slice-ansi: 5.0.0 string-width: 5.1.2 - cli-truncate@5.1.1: - dependencies: - slice-ansi: 7.1.2 - string-width: 8.1.1 - cli-welcome@2.2.3(encoding@0.1.13)(react@19.1.1): dependencies: chalk: 2.4.2 @@ -19222,8 +19094,6 @@ snapshots: commander@11.1.0: {} - commander@14.0.3: {} - commander@2.20.3: {} commander@4.1.1: {} @@ -19967,8 +19837,6 @@ snapshots: env-paths@2.2.1: {} - environment@1.1.0: {} - err-code@2.0.3: {} error-ex@1.3.2: @@ -21151,8 +21019,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-func-name@2.0.2: {} get-intrinsic@1.3.0: @@ -21615,8 +21481,6 @@ snapshots: husky@8.0.3: {} - husky@9.1.7: {} - hyperdown@2.4.29: {} i18next-fs-backend@2.6.0: {} @@ -21855,10 +21719,6 @@ snapshots: is-fullwidth-code-point@4.0.0: {} - is-fullwidth-code-point@5.1.0: - dependencies: - get-east-asian-width: 1.4.0 - is-generator-fn@2.1.0: {} is-generator-function@1.1.0: @@ -22685,16 +22545,6 @@ snapshots: - enquirer - supports-color - lint-staged@16.2.7: - dependencies: - commander: 14.0.3 - listr2: 9.0.5 - micromatch: 4.0.8 - nano-spawn: 2.0.0 - pidtree: 0.6.0 - string-argv: 0.3.2 - yaml: 2.8.1 - listr2@6.6.1: dependencies: cli-truncate: 3.1.0 @@ -22704,15 +22554,6 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 8.1.0 - listr2@9.0.5: - dependencies: - cli-truncate: 5.1.1 - colorette: 2.0.20 - eventemitter3: 5.0.1 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 9.0.2 - loader-runner@4.3.0: {} local-pkg@0.5.1: @@ -22785,14 +22626,6 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 8.1.0 - log-update@6.1.0: - dependencies: - ansi-escapes: 7.2.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.2 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.2 - logform@2.7.0: dependencies: '@colors/colors': 1.6.0 @@ -23538,8 +23371,6 @@ snapshots: mimic-fn@4.0.0: {} - mimic-function@5.0.1: {} - mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -23781,8 +23612,6 @@ snapshots: dependencies: lru-cache: 7.18.3 - nano-spawn@2.0.0: {} - nanoid@3.3.11: {} nanoid@5.1.3: {} @@ -24076,10 +23905,6 @@ snapshots: dependencies: mimic-fn: 4.0.0 - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - openai@4.61.0(encoding@0.1.13)(zod@4.1.12): dependencies: '@types/node': 18.19.80 @@ -24110,14 +23935,8 @@ snapshots: transitivePeerDependencies: - encoding - openapi-fetch@0.14.1: - dependencies: - openapi-typescript-helpers: 0.0.15 - openapi-types@12.1.3: {} - openapi-typescript-helpers@0.0.15: {} - opener@1.5.2: {} option@0.2.4: {} @@ -25281,11 +25100,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - ret@0.4.3: {} retry@0.12.0: {} @@ -25689,11 +25503,6 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 - slice-ansi@7.1.2: - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 5.1.0 - smart-buffer@4.2.0: {} socks-proxy-agent@8.0.5: @@ -25854,17 +25663,6 @@ snapshots: emoji-regex: 10.4.0 strip-ansi: 7.1.0 - string-width@7.2.0: - dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.0 - - string-width@8.1.1: - dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.0 - string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -27361,12 +27159,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.1.0 - wrappy@1.0.2: {} write-file-atomic@3.0.3: @@ -27412,7 +27204,8 @@ snapshots: yaml@2.8.0: {} - yaml@2.8.1: {} + yaml@2.8.1: + optional: true yargs-parser@18.1.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 30a40e12c5..ded8f53e08 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,8 @@ packages: - packages/* - - projects/* + - projects/app + - projects/marketplace + - projects/mcp_server + - projects/sandbox - scripts/icon - sdk/* diff --git a/projects/sandbox_server/.dockerignore b/projects/sandbox_server/.dockerignore new file mode 100644 index 0000000000..bcb386e17b --- /dev/null +++ b/projects/sandbox_server/.dockerignore @@ -0,0 +1,28 @@ +# Dependencies +node_modules + +# Git +.git +.gitignore + +# IDE +.vscode +.idea + +# Test files +test +*.test.ts +vitest.config.ts + +# Environment files +.env +.env.local +.env.*.local + +# Build artifacts +dist +*.log + +# Documentation +*.md +!README.md diff --git a/projects/sandbox_server/.env.template b/projects/sandbox_server/.env.template new file mode 100644 index 0000000000..ea0fd07af0 --- /dev/null +++ b/projects/sandbox_server/.env.template @@ -0,0 +1,18 @@ +# Server Configuration +PORT=3000 +# API Authentication Token +TOKEN=your-secret-token + +# Sealos Configuration +SEALOS_BASE_URL=https://applaunchpad.hzh.sealos.run +SEALOS_KC= + +# Container Configuration (fixed for all containers) +CONTAINER_IMAGE=hub.hzh.sealos.run/ns-4gabgrbc/agent-sandbox:v0.0.7 +CONTAINER_PORT=8080 +CONTAINER_CPU=0.5 +CONTAINER_MEMORY=1 +# Entrypoint format: JSON array like '["/bin/bash","-c","script.sh"]' or plain command +CONTAINER_ENTRYPOINT='["/bin/bash -c","/home/devbox/project/entrypoint.sh prod"]' +# Whether to expose container to public domain +CONTAINER_EXPOSES_PUBLIC_DOMAIN=true \ No newline at end of file diff --git a/projects/sandbox_server/.gitignore b/projects/sandbox_server/.gitignore new file mode 100644 index 0000000000..98874cdb4a --- /dev/null +++ b/projects/sandbox_server/.gitignore @@ -0,0 +1,32 @@ +# Dependencies +node_modules + +# Environment files +.env +.env.local +.env.*.local +.env.test + +# Build +dist +*.tsbuildinfo + +# Logs +*.log +npm-debug.log* + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage + +# Bun +bun.lockb diff --git a/projects/sandbox_server/Dockerfile b/projects/sandbox_server/Dockerfile new file mode 100644 index 0000000000..108e86987c --- /dev/null +++ b/projects/sandbox_server/Dockerfile @@ -0,0 +1,64 @@ +# ==================== Base ==================== +FROM oven/bun:1 AS base +WORKDIR /app + +# ==================== Install All Dependencies ==================== +FROM base AS deps + +# Copy package files +COPY package.json bun.lock* ./ + +# Install all dependencies (including devDependencies for build) +RUN bun install --frozen-lockfile + +# ==================== Build ==================== +FROM deps AS build + +# Copy source code and config +COPY src ./src +COPY tsconfig.json ./ + +# Build the application +RUN bun build src/index.ts --outdir=dist --target=bun --minify + +# ==================== Production Dependencies ==================== +FROM base AS prod-deps + +COPY package.json bun.lock* ./ + +# Install production dependencies only +RUN bun install --frozen-lockfile --production + +# ==================== Release ==================== +FROM oven/bun:1-slim AS release +WORKDIR /app + +# Copy production dependencies +COPY --from=prod-deps /app/node_modules ./node_modules + +# Copy built application +COPY --from=build /app/dist ./dist + +# Copy package.json for metadata +COPY package.json ./ + +# Create non-root user for security +RUN groupadd --system --gid 1001 nodejs && \ + useradd --system --uid 1001 --gid nodejs --no-create-home hono && \ + chown -R hono:nodejs /app + +USER hono + +# Set environment variables +ENV NODE_ENV=production +ENV PORT=3000 + +# Expose port +EXPOSE 3000 + +# Health check using bun fetch (no curl needed in slim image) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD bun -e "fetch('http://localhost:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" + +# Start the application +CMD ["bun", "run", "dist/index.js"] diff --git a/projects/sandbox_server/QUICKSTART.md b/projects/sandbox_server/QUICKSTART.md new file mode 100644 index 0000000000..5f81235861 --- /dev/null +++ b/projects/sandbox_server/QUICKSTART.md @@ -0,0 +1,231 @@ +# 快速开始指南 + +## ✅ 项目已完成 + +所有功能已实现并通过测试。 + +## 📁 项目结构 + +``` +sandbox_server/ +├── src/ +│ ├── index.ts # 应用入口 +│ ├── env.ts # 环境变量配置 +│ ├── schemas/ # Zod Schema 定义(类型导出) +│ │ ├── common.schema.ts # 公共 schema +│ │ ├── container.schema.ts # 容器 schema +│ │ └── sandbox.schema.ts # 沙盒 schema +│ ├── middleware/ # 中间件 +│ │ ├── auth.ts # Bearer token 鉴权 +│ │ └── error.ts # 统一错误处理 +│ ├── clients/ # 客户端(axios 实例) +│ │ ├── sealos.ts # Sealos API 客户端 +│ │ └── sandbox.ts # Sandbox 客户端 +│ ├── routes/ # 路由(OpenAPI 定义) +│ │ ├── container.route.ts # 容器生命周期路由 +│ │ └── sandbox.route.ts # 沙盒操作路由 +│ └── sdk/ # SDK 模块 +│ ├── container.ts # sdk.container.* +│ └── sandbox.ts # sdk.sandbox.* +├── test/ # 测试 +│ ├── setup.ts # 测试配置 +│ ├── .env.test.template # 测试环境变量模板 +│ └── app.test.ts # 基础测试 +├── Dockerfile # Docker 构建 +├── .env.template # 环境变量模板 +└── package.json +``` + +## 🚀 启动步骤 + +### 1. 安装依赖 + +```bash +cd FastGPT/projects/sandbox_server +bun install +``` + +### 2. 配置环境变量 + +复制 `.env.template` 为 `.env.local` 并填写配置: + +```bash +cp .env.template .env.local +``` + +编辑 `.env.local`: + +```env +PORT=3000 +TOKEN=your-secret-token +SEALOS_BASE_URL=https://your-sealos-api-url.com +SEALOS_KC=your-kubeconfig-token +``` + +### 3. 启动开发服务器 + +```bash +bun run dev +``` + +### 4. 访问 API 文档 + +- **Scalar UI**: http://localhost:3000/openapi/ui +- **OpenAPI JSON**: http://localhost:3000/openapi +- **健康检查**: http://localhost:3000/health + +## 📝 API 端点 + +### 容器生命周期 (`/v1/containers`) + +| 方法 | 路径 | 描述 | 鉴权 | +|------|------|------|------| +| POST | `/v1/containers` | 创建容器 | ✅ | +| GET | `/v1/containers/:name` | 获取容器信息 | ✅ | +| POST | `/v1/containers/:name/pause` | 暂停容器 | ✅ | +| POST | `/v1/containers/:name/start` | 启动容器 | ✅ | +| DELETE | `/v1/containers/:name` | 删除容器 | ✅ | + +### 沙盒操作 (`/v1/sandbox`) + +| 方法 | 路径 | 描述 | 鉴权 | +|------|------|------|------| +| POST | `/v1/sandbox/:name/exec` | 执行命令 | ✅ | +| GET | `/v1/sandbox/:name/health` | 健康检查 | ✅ | + +## 💡 SDK 使用示例 + +```typescript +import { createSDK } from './sdk'; + +const sdk = createSDK('http://localhost:3000', 'your-token'); + +// 创建容器 +await sdk.container.create({ + name: 'my-sandbox', + image: 'node:18-alpine', + resource: { cpu: 1, memory: 1024 } +}); + +// 获取容器信息 +const info = await sdk.container.get('my-sandbox'); + +// 暂停容器 +await sdk.container.pause('my-sandbox'); + +// 启动容器 +await sdk.container.start('my-sandbox'); + +// 执行命令 +const result = await sdk.sandbox.exec('my-sandbox', { + command: 'ls -la', + cwd: '/app' +}); +console.log(result.stdout); + +// 健康检查 +const healthy = await sdk.sandbox.health('my-sandbox'); + +// 删除容器 +await sdk.container.delete('my-sandbox'); +``` + +## 🧪 测试 + +### 单元测试 + +```bash +# 运行所有测试 +bun run test + +# 运行单次测试 +bun run test:run + +# 类型检查 +bun run typecheck +``` + +### 集成测试 + +集成测试需要真实的 Sealos 环境。 + +1. 配置测试环境变量: + +```bash +cp test/.env.test.template test/.env.test.local +# 编辑 test/.env.test.local,填写真实配置 +``` + +2. 运行集成测试: + +```bash +RUN_INTEGRATION_TESTS=true bun run test +``` + +详细说明请查看 [`test/README.md`](test/README.md) + +## 🐳 Docker 部署 + +```bash +# 构建镜像 +docker build -t sandbox-server . + +# 运行容器 +docker run -p 3000:3000 --env-file .env.local sandbox-server +``` + +## ✨ 特性 + +- ✅ **Bun 运行时**: 快速的包管理和执行 +- ✅ **Hono 框架**: 轻量级高性能 HTTP 框架 +- ✅ **Zod 类型验证**: 所有入参出参均使用 zod parse +- ✅ **OpenAPI 文档**: 自动生成 API 文档(使用 @hono/zod-openapi) +- ✅ **Scalar UI**: 现代化 API 文档界面 +- ✅ **类型安全 SDK**: 提供完整的 TypeScript 类型支持 +- ✅ **Bearer Token 鉴权**: 统一的认证中间件 +- ✅ **统一错误处理**: 避免 API 报错时未响应 +- ✅ **工厂模式**: 优雅的控制器设计 +- ✅ **Axios 客户端**: 为不同场景定制的 axios 实例 +- ✅ **Vitest 测试**: 单元测试和集成测试支持 + +## 📦 核心依赖 + +- `hono` - HTTP 框架 +- `@hono/zod-openapi` - OpenAPI 集成 +- `@scalar/hono-api-reference` - API 文档 UI +- `@t3-oss/env-core` - 环境变量管理 +- `axios` - HTTP 客户端 +- `zod` - Schema 验证 +- `vitest` - 测试框架 + +## 🔧 环境变量 + +| 变量 | 必填 | 描述 | 默认值 | +|------|------|------|--------| +| `PORT` | ❌ | 服务器端口 | 3000 | +| `TOKEN` | ✅ | API 认证 token | - | +| `SEALOS_BASE_URL` | ✅ | Sealos API 地址 | - | +| `SEALOS_KC` | ✅ | Sealos Kubeconfig | - | + +## 📞 问题排查 + +### 1. 依赖安装失败 + +```bash +rm -rf node_modules bun.lockb +bun install +``` + +### 2. 类型错误 + +```bash +bun run typecheck +``` + +### 3. 测试失败 + +确保测试环境变量已正确设置(见 `test/setup.ts`) + +--- + +🎉 **项目已完成!所有功能均已实现并通过测试。** diff --git a/projects/sandbox_server/READMD.md b/projects/sandbox_server/READMD.md new file mode 100644 index 0000000000..931b98a163 --- /dev/null +++ b/projects/sandbox_server/READMD.md @@ -0,0 +1,94 @@ +# FastGPT Sandbox Server + +借助 Sealos 的部署能力,进行快速的沙盒管理以及使用。 + +## 功能 + +- **容器生命周期管理**: 创建、暂停、启动、删除容器 +- **沙盒操作**: 在沙盒中执行命令、健康检查 +- **OpenAPI 文档**: 自动生成 API 文档 +- **SDK**: 提供类型安全的 SDK 调用 + +## 快速开始 + +### 安装依赖 + +```bash +bun install +``` + +### 配置环境变量 + +复制 `.env.template` 为 `.env.local` 并填写配置: + +```bash +cp .env.template .env.local +``` + +### 启动开发服务器 + +```bash +bun run dev +``` + +### 运行测试 + +```bash +bun run test +``` + +## API 文档 + +启动服务后访问: +- OpenAPI JSON: `http://localhost:3000/openapi` +- Scalar UI: `http://localhost:3000/openapi/ui` + +## API 端点 + +### 容器生命周期 (`/v1/containers`) + +| 方法 | 路径 | 描述 | +|------|------|------| +| POST | `/v1/containers` | 创建容器 | +| GET | `/v1/containers/:name` | 获取容器信息 | +| POST | `/v1/containers/:name/pause` | 暂停容器 | +| POST | `/v1/containers/:name/start` | 启动容器 | +| DELETE | `/v1/containers/:name` | 删除容器 | + +### 沙盒操作 (`/v1/sandbox`) + +| 方法 | 路径 | 描述 | +|------|------|------| +| POST | `/v1/sandbox/:name/exec` | 执行命令 | +| GET | `/v1/sandbox/:name/health` | 健康检查 | + +## SDK 使用 + +```typescript +import { createSDK } from 'sandbox-server/sdk'; + +const sdk = createSDK('http://localhost:3000', 'your-token'); + +// 容器操作 +await sdk.container.create({ + name: 'my-sandbox', + image: 'node:18-alpine', + resource: { cpu: 1, memory: 1024 } +}); + +const info = await sdk.container.get('my-sandbox'); +await sdk.container.pause('my-sandbox'); +await sdk.container.start('my-sandbox'); +await sdk.container.delete('my-sandbox'); + +// 沙盒操作 +const healthy = await sdk.sandbox.health('my-sandbox'); +const result = await sdk.sandbox.exec('my-sandbox', { command: 'ls -la' }); +``` + +## Docker 构建 + +```bash +docker build -t sandbox-server . +docker run -p 3000:3000 --env-file .env.local sandbox-server +``` diff --git a/projects/sandbox_server/bun.lock b/projects/sandbox_server/bun.lock new file mode 100644 index 0000000000..720086aa82 --- /dev/null +++ b/projects/sandbox_server/bun.lock @@ -0,0 +1,463 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "sandbox-server", + "dependencies": { + "@hono/zod-openapi": "^1.2.1", + "@scalar/hono-api-reference": "^0.9.40", + "@t3-oss/env-core": "^0.13.10", + "axios": "^1.7.0", + "hono": "^4.11.7", + "zod": "^4.0.0", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/nock": "^11.1.0", + "@vitest/coverage-v8": "^3.0.9", + "nock": "^14.0.10", + "typescript": "^5.0.0", + "vitest": "^3.0.0", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@8.4.0", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-Ckp971tmTw4pnv+o7iK85ldBHBKk6gxMaoNyLn3c2Th/fKoTG8G3jdYuOanpdGqwlDB0z01FOjry2d32lfTqrA=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + + "@hono/zod-openapi": ["@hono/zod-openapi@1.2.1", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^8.1.0", "@hono/zod-validator": "^0.7.6", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "^4.0.0" } }, "sha512-aZza4V8wkqpdHBWFNPiCeWd0cGOXbYuQW9AyezHs/jwQm5p67GkUyXwfthAooAwnG7thTpvOJkThZpCoY6us8w=="], + + "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@mswjs/interceptors": ["@mswjs/interceptors@0.39.8", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA=="], + + "@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], + + "@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="], + + "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + + "@scalar/core": ["@scalar/core@0.3.37", "", { "dependencies": { "@scalar/types": "0.6.2" } }, "sha512-cQWMHsGD9jCiYHi91acR3tOsj+qGk+dRQ2W+N5+au1NZ/GkUNT5TUEufekn/sj1S8af+lOnn3y0xXoTI34jCog=="], + + "@scalar/helpers": ["@scalar/helpers@0.2.11", "", {}, "sha512-Y7DLt1bIZF9dvHzJwSJTcC1lpSr1Tbf4VBhHOCRIHu23Rr7/lhQnddRxFmPV1tZXwEQKz7F7yRrubwCfKPCucw=="], + + "@scalar/hono-api-reference": ["@scalar/hono-api-reference@0.9.40", "", { "dependencies": { "@scalar/core": "0.3.37" }, "peerDependencies": { "hono": "^4.11.5" } }, "sha512-0tQOxyEwuu1QGcoA5aCJg2eSmNfF35mxeGx13TND9ud5ZBeuOqli8jyfykgkqV3gFTnDDlQYgQcOvB6Rgk2beA=="], + + "@scalar/types": ["@scalar/types@0.6.2", "", { "dependencies": { "@scalar/helpers": "0.2.11", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" } }, "sha512-VWfY/z9R5NT8PpKVmvmIj6QSh56MMcl8x3JsGiNxR+w7txGQEq+QzEl35aU56uSBFmLfPk1oyInoaHhkosKooA=="], + + "@t3-oss/env-core": ["@t3-oss/env-core@0.13.10", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g=="], + + "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/nock": ["@types/nock@11.1.0", "", { "dependencies": { "nock": "*" } }, "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw=="], + + "@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="], + + "@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.4", "vitest": "3.2.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ=="], + + "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], + + "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.11", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "nock": ["nock@14.0.10", "", { "dependencies": { "@mswjs/interceptors": "^0.39.5", "json-stringify-safe": "^5.0.1", "propagate": "^2.0.0" } }, "sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw=="], + + "openapi3-ts": ["openapi3-ts@4.5.0", "", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], + + "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "propagate": ["propagate@2.0.1", "", {}, "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], + + "type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], + + "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "@scalar/types/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/projects/sandbox_server/package.json b/projects/sandbox_server/package.json new file mode 100644 index 0000000000..72c9be77e0 --- /dev/null +++ b/projects/sandbox_server/package.json @@ -0,0 +1,34 @@ +{ + "name": "sandbox-server", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "bun run --watch src/index.ts", + "build": "tsc --noEmit && bun build src/index.ts --outdir=dist --target=bun", + "start": "bun run src/index.ts", + "start:prod": "bun run dist/index.js", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "typecheck": "tsc --noEmit" + }, + "exports": { + ".": "./src/index.ts", + "./sdk": "./src/sdk/index.ts" + }, + "dependencies": { + "@hono/zod-openapi": "^1.2.1", + "@scalar/hono-api-reference": "^0.9.40", + "@t3-oss/env-core": "^0.13.10", + "axios": "^1.7.0", + "hono": "^4.11.7", + "zod": "^4.1.12" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/nock": "^11.1.0", + "@vitest/coverage-v8": "^3.0.9", + "nock": "^14.0.10", + "typescript": "^5.0.0", + "vitest": "^3.0.0" + } +} diff --git a/projects/sandbox_server/sdk/.gitignore b/projects/sandbox_server/sdk/.gitignore new file mode 100644 index 0000000000..999729dba2 --- /dev/null +++ b/projects/sandbox_server/sdk/.gitignore @@ -0,0 +1,4 @@ +dist +node_modules +*.log +.DS_Store diff --git a/projects/sandbox_server/sdk/BUILD.md b/projects/sandbox_server/sdk/BUILD.md new file mode 100644 index 0000000000..6b0fec935c --- /dev/null +++ b/projects/sandbox_server/sdk/BUILD.md @@ -0,0 +1,123 @@ +# SDK 构建说明 + +## 构建步骤 + +### 1. 安装依赖 + +```bash +cd /Volumes/code/fastgpt-pro/FastGPT/projects/sandbox_server/sdk +pnpm install +``` + +### 2. 构建 SDK + +```bash +pnpm run build +``` + +这将生成以下文件到 `dist` 目录: +- `index.js` - ESM 格式 +- `index.cjs` - CommonJS 格式 +- `index.d.ts` - TypeScript 类型定义 +- 对应的 sourcemap 文件 + +### 3. 开发模式 + +在开发过程中,可以使用 watch 模式自动重新构建: + +```bash +pnpm run dev +``` + +### 4. 类型检查 + +```bash +pnpm run typecheck +``` + +## 发布到 npm + +### 1. 登录 npm + +```bash +npm login +``` + +### 2. 发布 + +```bash +pnpm publish +``` + +发布前会自动运行 `prepublishOnly` 脚本进行构建。 + +## 本地测试 + +### 1. 创建本地链接 + +```bash +cd /Volumes/code/fastgpt-pro/FastGPT/projects/sandbox_server/sdk +pnpm link --global +``` + +### 2. 在其他项目中使用 + +```bash +cd /path/to/your/project +pnpm link --global @fastgpt-sdk/sandbox_server +``` + +### 3. 测试完成后取消链接 + +```bash +pnpm unlink --global @fastgpt-sdk/sandbox_server +``` + +## 目录结构 + +``` +sdk/ +├── dist/ # 构建输出目录 +├── index.ts # SDK 入口文件 +├── container.ts # 容器管理 SDK +├── sandbox.ts # 沙盒执行 SDK +├── package.json # 包配置 +├── tsconfig.json # TypeScript 配置 +├── tsup.config.ts # 构建配置 +├── README.md # 使用文档 +├── BUILD.md # 构建文档 +└── .gitignore # Git 忽略文件 +``` + +## 配置说明 + +### package.json + +- `name`: @fastgpt-sdk/sandbox_server +- `main`: CommonJS 入口 +- `module`: ESM 入口 +- `types`: TypeScript 类型定义入口 +- `exports`: 导出配置,支持多种模块格式 + +### tsup.config.ts + +- `entry`: 入口文件 +- `format`: 输出格式 (esm, cjs) +- `dts`: 生成 TypeScript 类型定义 +- `clean`: 构建前清理输出目录 +- `sourcemap`: 生成 sourcemap +- `external`: 外部依赖(不打包到 bundle 中) + +## 依赖说明 + +### dependencies +- `axios`: HTTP 客户端 +- `zod`: 运行时类型验证 + +### devDependencies +- `tsup`: TypeScript 打包工具 +- `typescript`: TypeScript 编译器 +- `@types/node`: Node.js 类型定义 + +### peerDependencies +确保使用 SDK 的项目也安装了相同版本的 axios 和 zod。 diff --git a/projects/sandbox_server/sdk/bun.lock b/projects/sandbox_server/sdk/bun.lock new file mode 100644 index 0000000000..94ef435726 --- /dev/null +++ b/projects/sandbox_server/sdk/bun.lock @@ -0,0 +1,270 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@fastgpt-sdk/sandbox_server", + "dependencies": { + "axios": "^1.7.0", + "zod": "^3.22.0", + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + }, + "peerDependencies": { + "axios": "^1.7.0", + "zod": "^3.22.0", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + + "@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@20.19.31", "https://registry.npmmirror.com/@types/node/-/node-20.19.31.tgz", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A=="], + + "acorn": ["acorn@8.15.0", "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "any-promise": ["any-promise@1.3.0", "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.13.4", "https://registry.npmmirror.com/axios/-/axios-1.13.4.tgz", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="], + + "bundle-require": ["bundle-require@5.1.0", "https://registry.npmmirror.com/bundle-require/-/bundle-require-5.1.0.tgz", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "chokidar": ["chokidar@4.0.3", "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@4.1.1", "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "confbox": ["confbox@0.1.8", "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.27.2", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + + "fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "https://registry.npmmirror.com/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "follow-redirects": ["follow-redirects@1.15.11", "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "form-data": ["form-data@4.0.5", "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "joycon": ["joycon@3.1.1", "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "lilconfig": ["lilconfig@3.1.3", "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "https://registry.npmmirror.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mlly": ["mlly@1.8.0", "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pirates": ["pirates@4.0.7", "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "readdirp": ["readdirp@4.1.2", "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "resolve-from": ["resolve-from@5.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + + "source-map": ["source-map@0.7.6", "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "sucrase": ["sucrase@3.35.1", "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "thenify": ["thenify@3.3.1", "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinyexec": ["tinyexec@0.3.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tree-kill": ["tree-kill@1.2.2", "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsup": ["tsup@8.5.1", "https://registry.npmmirror.com/tsup/-/tsup-8.5.1.tgz", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@6.21.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + } +} diff --git a/projects/sandbox_server/sdk/container.ts b/projects/sandbox_server/sdk/container.ts new file mode 100644 index 0000000000..975c4580f1 --- /dev/null +++ b/projects/sandbox_server/sdk/container.ts @@ -0,0 +1,75 @@ +import axios, { type AxiosInstance } from 'axios'; +import { + CreateContainerSchema, + ContainerInfoResponseSchema, + SuccessResponseSchema +} from './schemas'; +import type { CreateContainerInput, ContainerInfo } from './types'; + +/** + * Container SDK + * Provides type-safe API calls for container lifecycle management + */ +export class ContainerSDK { + private readonly client: AxiosInstance; + + constructor(baseUrl: string, token: string) { + this.client = axios.create({ + baseURL: `${baseUrl.replace(/\/$/, '')}/v1`, + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }); + } + + /** + * Create a new container + */ + async create(params: CreateContainerInput): Promise { + const validated = CreateContainerSchema.parse(params); + const response = await this.client.post('/containers', validated); + SuccessResponseSchema.parse(response.data); + } + + /** + * Get container information + */ + async get(name: string): Promise { + try { + const response = await this.client.get(`/containers/${encodeURIComponent(name)}`); + const result = ContainerInfoResponseSchema.parse(response.data); + return result.data; + } catch (err) { + if (axios.isAxiosError(err) && err.response?.status === 404) { + return null; + } + throw err; + } + } + + /** + * Pause a running container + */ + async pause(name: string): Promise { + const response = await this.client.post(`/containers/${encodeURIComponent(name)}/pause`); + SuccessResponseSchema.parse(response.data); + } + + /** + * Start a paused container + */ + async start(name: string): Promise { + const response = await this.client.post(`/containers/${encodeURIComponent(name)}/start`); + SuccessResponseSchema.parse(response.data); + } + + /** + * Delete a container + */ + async delete(name: string): Promise { + const response = await this.client.delete(`/containers/${encodeURIComponent(name)}`); + SuccessResponseSchema.parse(response.data); + } +} diff --git a/projects/sandbox_server/sdk/index.ts b/projects/sandbox_server/sdk/index.ts new file mode 100644 index 0000000000..6846bda78b --- /dev/null +++ b/projects/sandbox_server/sdk/index.ts @@ -0,0 +1,67 @@ +import { ContainerSDK } from './container'; +import { SandboxSDK } from './sandbox'; + +export { ContainerSDK } from './container'; +export { SandboxSDK } from './sandbox'; + +// Export types (independent definitions, no external dependencies) +export type { + CreateContainerInput, + ContainerInfo, + ContainerStatus, + ContainerServer, + ExecRequest, + ExecResponse +} from './types'; + +/** + * SDK Configuration + */ +export interface SDKConfig { + baseUrl: string; + token: string; +} + +/** + * Sandbox Server SDK + * Provides type-safe API calls for all sandbox server operations + * + * @example + * ```typescript + * import { createSDK } from 'sandbox-server/sdk'; + * + * const sdk = createSDK('http://localhost:3000', 'your-token'); + * + * // Container lifecycle + * await sdk.container.create({ name: 'test', image: 'node:18', resource: { cpu: 1, memory: 1024 } }); + * const info = await sdk.container.get('test'); + * await sdk.container.pause('test'); + * await sdk.container.start('test'); + * await sdk.container.delete('test'); + * + * // Sandbox operations + * const healthy = await sdk.sandbox.health('test'); + * const result = await sdk.sandbox.exec('test', { command: 'ls -la' }); + * ``` + */ +export interface SandboxServerSDK { + container: ContainerSDK; + sandbox: SandboxSDK; +} + +/** + * Create a new SDK instance + */ +export function createSDK(baseUrl: string, token: string): SandboxServerSDK { + return { + container: new ContainerSDK(baseUrl, token), + sandbox: new SandboxSDK(baseUrl, token) + }; +} + +/** + * Create a new SDK instance from config + */ +export function createSDKFromConfig(config: SDKConfig): SandboxServerSDK { + return createSDK(config.baseUrl, config.token); +} diff --git a/projects/sandbox_server/sdk/package.json b/projects/sandbox_server/sdk/package.json new file mode 100644 index 0000000000..a569450ace --- /dev/null +++ b/projects/sandbox_server/sdk/package.json @@ -0,0 +1,50 @@ +{ + "name": "@fastgpt-sdk/sandbox-server", + "version": "0.0.5", + "description": "Type-safe SDK for FastGPT Sandbox Server API", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "clean": "rm -rf dist", + "build:js": "bun build index.ts --outdir dist --target node", + "build:dts": "tsc --emitDeclarationOnly --outDir dist", + "build": "bun run clean && bun run build:js && bun run build:dts", + "dev": "bun run --watch index.ts", + "typecheck": "tsc --noEmit", + "prepublishOnly": "bun run build" + }, + "keywords": [ + "fastgpt", + "sandbox", + "sdk", + "typescript" + ], + "license": "MIT", + "dependencies": { + "axios": "^1.7.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "peerDependencies": { + "axios": "^1.7.0", + "zod": "^3.22.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/projects/sandbox_server/sdk/sandbox.ts b/projects/sandbox_server/sdk/sandbox.ts new file mode 100644 index 0000000000..fe5327d2fb --- /dev/null +++ b/projects/sandbox_server/sdk/sandbox.ts @@ -0,0 +1,41 @@ +import axios, { type AxiosInstance } from 'axios'; +import { ExecRequestSchema, ExecResultResponseSchema, HealthCheckResponseSchema } from './schemas'; +import type { ExecRequest, ExecResponse } from './types'; + +/** + * Sandbox SDK + * Provides type-safe API calls for sandbox operations + */ +export class SandboxSDK { + private readonly client: AxiosInstance; + + constructor(baseUrl: string, token: string) { + this.client = axios.create({ + baseURL: `${baseUrl.replace(/\/$/, '')}/v1`, + timeout: 60000, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }); + } + + /** + * Execute a command in the sandbox + */ + async exec(name: string, params: ExecRequest): Promise { + const validated = ExecRequestSchema.parse(params); + const response = await this.client.post(`/sandbox/${encodeURIComponent(name)}/exec`, validated); + const result = ExecResultResponseSchema.parse(response.data); + return result.data; + } + + /** + * Check sandbox health + */ + async health(name: string): Promise { + const response = await this.client.get(`/sandbox/${encodeURIComponent(name)}/health`); + const result = HealthCheckResponseSchema.parse(response.data); + return result.healthy; + } +} diff --git a/projects/sandbox_server/sdk/schemas.ts b/projects/sandbox_server/sdk/schemas.ts new file mode 100644 index 0000000000..a2b1663449 --- /dev/null +++ b/projects/sandbox_server/sdk/schemas.ts @@ -0,0 +1,71 @@ +/** + * SDK Schemas for runtime validation + * Uses standard zod (not @hono/zod-openapi) + */ +import { z } from 'zod'; + +// ==================== Common Schemas ==================== + +export const SuccessResponseSchema = z.object({ + success: z.literal(true) +}); + +// ==================== Container Schemas ==================== + +export const CreateContainerSchema = z.object({ + name: z.string().min(1) +}); + +const ContainerStatusSchema = z.object({ + state: z.enum(['Running', 'Creating', 'Paused', 'Error', 'Unknown']), + replicas: z.number().optional(), + availableReplicas: z.number().optional() +}); + +const ContainerServerSchema = z.object({ + serviceName: z.string(), + number: z.number(), + publicDomain: z.string().optional(), + domain: z.string().optional() +}); + +const ContainerInfoSchema = z.object({ + name: z.string(), + image: z.object({ + imageName: z.string() + }), + status: ContainerStatusSchema, + server: ContainerServerSchema.optional(), + createdAt: z.string().optional() +}); + +export const ContainerInfoResponseSchema = z.object({ + success: z.literal(true), + data: ContainerInfoSchema +}); + +// ==================== Sandbox Schemas ==================== + +export const ExecRequestSchema = z.object({ + command: z.string().min(1), + cwd: z.string().optional() +}); + +const ExecResponseSchema = z.object({ + success: z.boolean(), + stdout: z.string(), + stderr: z.string(), + exitCode: z.number(), + cwd: z.string().optional(), + error: z.string().optional() +}); + +export const HealthCheckResponseSchema = z.object({ + success: z.literal(true), + healthy: z.boolean() +}); + +export const ExecResultResponseSchema = z.object({ + success: z.literal(true), + data: ExecResponseSchema +}); diff --git a/projects/sandbox_server/sdk/tsconfig.json b/projects/sandbox_server/sdk/tsconfig.json new file mode 100644 index 0000000000..52fdcd2205 --- /dev/null +++ b/projects/sandbox_server/sdk/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": ".", + "declaration": true, + "emitDeclarationOnly": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules", "dist", "example.ts"] +} diff --git a/projects/sandbox_server/sdk/types.ts b/projects/sandbox_server/sdk/types.ts new file mode 100644 index 0000000000..b27d67faed --- /dev/null +++ b/projects/sandbox_server/sdk/types.ts @@ -0,0 +1,49 @@ +/** + * SDK Type Definitions + * Independent type definitions for SDK users (no external dependencies) + */ + +// ==================== Container Types ==================== + +export interface CreateContainerInput { + name: string; +} + +export interface ContainerStatus { + state: 'Running' | 'Creating' | 'Paused' | 'Error' | 'Unknown'; + replicas?: number; + availableReplicas?: number; +} + +export interface ContainerServer { + serviceName: string; + number: number; + publicDomain?: string; + domain?: string; +} + +export interface ContainerInfo { + name: string; + image: { + imageName: string; + }; + status: ContainerStatus; + server?: ContainerServer; + createdAt?: string; +} + +// ==================== Sandbox Types ==================== + +export interface ExecRequest { + command: string; + cwd?: string; +} + +export interface ExecResponse { + success: boolean; + stdout: string; + stderr: string; + exitCode: number; + cwd?: string; + error?: string; +} diff --git a/projects/sandbox_server/src/clients/index.ts b/projects/sandbox_server/src/clients/index.ts new file mode 100644 index 0000000000..57e992be2d --- /dev/null +++ b/projects/sandbox_server/src/clients/index.ts @@ -0,0 +1,2 @@ +export { SealosClient, createSealosClient } from './sealos'; +export { SandboxClient, createSandboxClient } from './sandbox'; diff --git a/projects/sandbox_server/src/clients/sandbox.ts b/projects/sandbox_server/src/clients/sandbox.ts new file mode 100644 index 0000000000..9d761ce1c8 --- /dev/null +++ b/projects/sandbox_server/src/clients/sandbox.ts @@ -0,0 +1,100 @@ +import axios, { type AxiosInstance, type AxiosError } from 'axios'; +import { + ExecRequestSchema, + ExecResponseSchema, + HealthResponseSchema, + type ExecRequest, + type ExecResponse, + type HealthResponse +} from '../schemas'; + +const DEFAULT_CWD = '/app/sandbox'; + +/** + * Sandbox Client + * Communicates with the sandbox server running inside container + */ +export class SandboxClient { + private readonly client: AxiosInstance; + private readonly baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl.replace(/\/$/, ''); + + this.client = axios.create({ + baseURL: this.baseUrl, + timeout: 60000, // 60s timeout for long-running commands + headers: { + 'Content-Type': 'application/json' + } + }); + + // Response interceptor for error handling + this.client.interceptors.response.use( + (response) => response, + (error: AxiosError<{ error?: string }>) => { + if (error.code === 'ECONNREFUSED') { + return Promise.reject(new Error('Sandbox server is not reachable')); + } + + if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') { + return Promise.reject(new Error('Request timeout')); + } + + const status = error.response?.status; + if (status === 404) { + return Promise.reject(new Error('Endpoint not found')); + } + + const responseData = { + status: error.response?.status, + statusText: error.response?.statusText, + message: error.response?.data?.error || error.message || 'Request failed', + data: error.response?.data + }; + return Promise.reject(responseData); + } + ); + } + + /** + * Check if sandbox server is healthy + */ + async health(): Promise { + const response = await this.client.get('/health'); + return HealthResponseSchema.parse(response.data); + } + + /** + * Check if sandbox is healthy (boolean) + */ + async isHealthy(): Promise { + try { + const result = await this.health(); + return result.status === 'ok'; + } catch { + return false; + } + } + + /** + * Execute a shell command in the sandbox + */ + async exec(params: ExecRequest): Promise { + const validated = ExecRequestSchema.parse(params); + + const response = await this.client.post('/exec', { + command: validated.command, + cwd: validated.cwd || DEFAULT_CWD + }); + + return ExecResponseSchema.parse(response.data); + } +} + +/** + * Create a new SandboxClient instance + */ +export function createSandboxClient(baseUrl: string): SandboxClient { + return new SandboxClient(baseUrl); +} diff --git a/projects/sandbox_server/src/clients/sealos.ts b/projects/sandbox_server/src/clients/sealos.ts new file mode 100644 index 0000000000..8bab578a77 --- /dev/null +++ b/projects/sandbox_server/src/clients/sealos.ts @@ -0,0 +1,223 @@ +import axios, { type AxiosInstance, type AxiosError } from 'axios'; +import { + CreateContainerSchema, + SealosContainerResponseSchema, + type CreateContainerInput, + type ContainerInfo, + type ContainerStatus +} from '../schemas'; +import { env, containerConfig } from '../env'; + +/** + * Sealos API Client + * Handles container lifecycle management through Sealos API + */ +export class SealosClient { + private readonly client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: env.SEALOS_BASE_URL, + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${env.SEALOS_KC}` + } + }); + + // Response interceptor for error handling + this.client.interceptors.response.use( + (response) => { + // Handle empty response for void operations + if (response.data === undefined || response.data === null) { + return response; + } + + // Check API-level error code + if (response.data.code === 404) { + return Promise.reject({ status: 404, message: 'Resource not found' }); + } + + if (response.data.code && response.data.code !== 200) { + return Promise.reject(response.data.error || response.data.message || 'API error'); + } + + return response; + }, + (error: AxiosError<{ error?: string; message?: string }>) => { + const status = error.response?.status; + const errorData = error.response?.data; + console.log(errorData, 2222); + if (status === 401 || status === 403) { + return Promise.reject(new Error('Authentication failed')); + } + + if (status === 404) { + return Promise.reject({ + status: 404, + message: errorData?.message || 'Resource not found' + }); + } + + if (status && status >= 500) { + return Promise.reject(new Error(errorData?.message || 'Server error')); + } + + const message = errorData?.error || errorData?.message || error.message || 'Request failed'; + return Promise.reject(new Error(message)); + } + ); + } + + /** + * Create a new container with fixed configuration from environment variables + */ + async createContainer(params: CreateContainerInput): Promise { + const validated = CreateContainerSchema.parse(params); + + // Parse entrypoint configuration + let launchCommand: { command?: string; args?: string } | undefined; + if (containerConfig.entrypoint) { + try { + const parsed = JSON.parse(containerConfig.entrypoint); + if (Array.isArray(parsed) && parsed.length > 0) { + launchCommand = { + command: parsed[0], + args: parsed.slice(1).join(' ') + }; + } + } catch { + // If not JSON, treat as direct command + launchCommand = { command: containerConfig.entrypoint }; + } + } + + await this.client + .post('/api/v1/app', { + name: validated.name, + image: { + imageName: containerConfig.image + }, + resource: { + cpu: containerConfig.cpu, + memory: containerConfig.memory, + replicas: 1 + }, + ports: [ + { + number: containerConfig.port, + exposesPublicDomain: containerConfig.exposesPublicDomain + } + ], + launchCommand + }) + .catch((err) => { + if (err.code === 409) { + return; + } + + return Promise.reject(err); + }); + } + + /** + * Get container information by name + */ + async getContainer(name: string): Promise { + try { + const response = await this.client.get(`/api/v1/app/${encodeURIComponent(name)}`); + const data = SealosContainerResponseSchema.parse(response.data.data); + + return { + name: data.name, + image: data.image, + status: this.mapContainerStatus(data.status), + server: data.ports[0], + createdAt: data.createTime + }; + } catch (err: unknown) { + if (err && typeof err === 'object' && 'status' in err && err.status === 404) { + return null; + } + throw err; + } + } + + /** + * Pause a running container + */ + async pauseContainer(name: string): Promise { + try { + await this.client.post(`/api/v1/app/${encodeURIComponent(name)}/pause`); + } catch (err: unknown) { + if (err && typeof err === 'object' && 'status' in err && err.status === 404) { + return; + } + throw err; + } + } + + /** + * Resume/start a paused container + */ + async resumeContainer(name: string): Promise { + try { + await this.client.post(`/api/v1/app/${encodeURIComponent(name)}/start`); + } catch (err: unknown) { + if (err && typeof err === 'object' && 'status' in err && err.status === 404) { + return; + } + throw err; + } + } + + /** + * Delete a container + */ + async deleteContainer(name: string): Promise { + try { + await this.client.delete(`/api/v1/app/${encodeURIComponent(name)}`); + } catch (err: unknown) { + if (err && typeof err === 'object' && 'status' in err && err.status === 404) { + return; + } + throw err; + } + } + + /** + * Map Sealos API status to internal status + */ + private mapContainerStatus(status: { + replicas: number; + availableReplicas: number; + isPause: boolean; + }): ContainerStatus { + if (status.isPause) { + return { + state: 'Paused', + replicas: status.replicas, + availableReplicas: status.availableReplicas + }; + } + if (status.availableReplicas > 0) { + return { + state: 'Running', + replicas: status.replicas, + availableReplicas: status.availableReplicas + }; + } + return { + state: 'Creating', + replicas: status.replicas, + availableReplicas: status.availableReplicas + }; + } +} + +/** + * Create a new SealosClient instance + */ +export function createSealosClient(): SealosClient { + return new SealosClient(); +} diff --git a/projects/sandbox_server/src/env.ts b/projects/sandbox_server/src/env.ts new file mode 100644 index 0000000000..39f1812b5e --- /dev/null +++ b/projects/sandbox_server/src/env.ts @@ -0,0 +1,34 @@ +import { createEnv } from '@t3-oss/env-core'; +import { z } from 'zod'; + +const isTest = process.env.NODE_ENV === 'test'; + +export const env = createEnv({ + server: { + PORT: z.coerce.number().default(3000), + TOKEN: isTest ? z.string().default('test-token') : z.string().min(1), + SEALOS_BASE_URL: z.string().url().default('https://applaunchpad.hzh.sealos.run'), + SEALOS_KC: isTest ? z.string().default('') : z.string().min(1), + // Container configuration + CONTAINER_IMAGE: isTest ? z.string().default('test-image') : z.string(), + CONTAINER_PORT: z.coerce.number().default(8080), + CONTAINER_CPU: z.coerce.number().default(0.5), + CONTAINER_MEMORY: z.coerce.number().default(1), + CONTAINER_ENTRYPOINT: z.string().optional(), + CONTAINER_EXPOSES_PUBLIC_DOMAIN: z + .string() + .default('false') + .transform((v) => v === 'true') + }, + runtimeEnv: process.env +}); + +// Container configuration for SealosClient +export const containerConfig = { + image: env.CONTAINER_IMAGE, + port: env.CONTAINER_PORT, + cpu: env.CONTAINER_CPU, + memory: env.CONTAINER_MEMORY, + entrypoint: env.CONTAINER_ENTRYPOINT || '', + exposesPublicDomain: env.CONTAINER_EXPOSES_PUBLIC_DOMAIN +}; diff --git a/projects/sandbox_server/src/index.ts b/projects/sandbox_server/src/index.ts new file mode 100644 index 0000000000..f32b3c651d --- /dev/null +++ b/projects/sandbox_server/src/index.ts @@ -0,0 +1,79 @@ +import { OpenAPIHono } from '@hono/zod-openapi'; +import { apiReference } from '@scalar/hono-api-reference'; +import { env } from './env'; +import { authMiddleware, errorHandler, loggerMiddleware } from './middleware'; +import { createSealosClient } from './clients'; +import { createContainerRoutes, createSandboxRoutes } from './routes'; +import { logger } from './utils'; + +// Create Hono app with OpenAPI support +const app = new OpenAPIHono(); + +// Global error handler +app.onError(errorHandler); + +// Global logger middleware +app.use('*', loggerMiddleware); + +// Create Sealos client +const sealosClient = createSealosClient(); + +// ==================== Public Routes ==================== + +// Health check endpoint (no auth required) +app.get('/health', (c) => { + return c.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// OpenAPI JSON document +app.doc('/openapi', { + openapi: '3.0.0', + info: { + title: 'Sandbox Server API', + version: '1.0.0', + description: 'API for managing sandbox containers via Sealos' + }, + servers: [ + { + url: `http://localhost:${env.PORT}`, + description: 'Local development server' + } + ] +}); + +// Scalar API Reference UI +app.get( + '/openapi/ui', + apiReference({ + url: '/openapi', + theme: 'default' + }) +); + +// ==================== Protected Routes ==================== + +// Create v1 router with authentication +const v1 = new OpenAPIHono(); +v1.use('*', authMiddleware); + +// Mount container routes +v1.route('/', createContainerRoutes(sealosClient)); + +// Mount sandbox routes +v1.route('/', createSandboxRoutes(sealosClient)); + +// Mount v1 router +app.route('/v1', v1); + +// ==================== Start Server ==================== + +logger.info('Server', `Starting on port ${env.PORT}`); +logger.info('Server', `API Documentation: http://localhost:${env.PORT}/openapi/ui`); + +export default { + port: env.PORT, + fetch: app.fetch +}; + +// Export app for testing +export { app }; diff --git a/projects/sandbox_server/src/middleware/auth.ts b/projects/sandbox_server/src/middleware/auth.ts new file mode 100644 index 0000000000..8d1a2297d2 --- /dev/null +++ b/projects/sandbox_server/src/middleware/auth.ts @@ -0,0 +1,29 @@ +import { createMiddleware } from 'hono/factory'; +import { HTTPException } from 'hono/http-exception'; +import { env } from '../env'; + +/** + * Bearer token authentication middleware + * Validates Authorization header: Bearer + */ +export const authMiddleware = createMiddleware(async (c, next) => { + const authorization = c.req.header('Authorization'); + + if (!authorization) { + throw new HTTPException(401, { message: 'Authorization header is required' }); + } + + if (!authorization.startsWith('Bearer ')) { + throw new HTTPException(401, { + message: 'Invalid authorization format. Expected: Bearer ' + }); + } + + const token = authorization.slice(7); + + if (token !== env.TOKEN) { + throw new HTTPException(401, { message: 'Invalid token' }); + } + + await next(); +}); diff --git a/projects/sandbox_server/src/middleware/error.ts b/projects/sandbox_server/src/middleware/error.ts new file mode 100644 index 0000000000..f072fc1b51 --- /dev/null +++ b/projects/sandbox_server/src/middleware/error.ts @@ -0,0 +1,47 @@ +import type { ErrorHandler } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import { ZodError } from 'zod'; +import { setLoggerError } from './logger'; + +/** + * Global error handler + * Catches all errors and returns consistent JSON response + */ +export const errorHandler: ErrorHandler = (err, c) => { + // Handle HTTP exceptions + if (err instanceof HTTPException) { + setLoggerError(c.req.raw, err.message); + return c.json( + { + success: false, + message: err.message + }, + err.status + ); + } + + // Handle Zod validation errors + if (err instanceof ZodError) { + const message = 'Validation error'; + setLoggerError(c.req.raw, message); + return c.json( + { + success: false, + message, + errors: err.issues + }, + 400 + ); + } + + // Handle generic errors + const message = err instanceof Error ? err.message : 'Internal Server Error'; + setLoggerError(c.req.raw, message); + return c.json( + { + success: false, + message + }, + 500 + ); +}; diff --git a/projects/sandbox_server/src/middleware/index.ts b/projects/sandbox_server/src/middleware/index.ts new file mode 100644 index 0000000000..f9a1eba90f --- /dev/null +++ b/projects/sandbox_server/src/middleware/index.ts @@ -0,0 +1,3 @@ +export { authMiddleware } from './auth'; +export { errorHandler } from './error'; +export { loggerMiddleware, setLoggerError } from './logger'; diff --git a/projects/sandbox_server/src/middleware/logger.ts b/projects/sandbox_server/src/middleware/logger.ts new file mode 100644 index 0000000000..eeaefbad04 --- /dev/null +++ b/projects/sandbox_server/src/middleware/logger.ts @@ -0,0 +1,44 @@ +import { createMiddleware } from 'hono/factory'; +import { logger } from '../utils'; + +// Store for request timing and error info +const requestStore = new WeakMap(); + +/** + * HTTP Logger middleware + * Logs request start and completion with timing information + */ +export const loggerMiddleware = createMiddleware(async (c, next) => { + const startTime = Date.now(); + const method = c.req.method; + const path = c.req.path; + + // Store timing info + requestStore.set(c.req.raw, { startTime }); + + // Log request start + logger.httpRequest(method, path); + + await next(); + + // Log response + const duration = Date.now() - startTime; + const status = c.res.status; + const stored = requestStore.get(c.req.raw); + const errorMessage = status >= 400 ? stored?.errorMessage : undefined; + + logger.httpResponse(method, path, status, duration, errorMessage); + + // Cleanup + requestStore.delete(c.req.raw); +}); + +/** + * Set error message for logging (called from errorHandler) + */ +export function setLoggerError(req: Request, message: string): void { + const stored = requestStore.get(req); + if (stored) { + stored.errorMessage = message; + } +} diff --git a/projects/sandbox_server/src/routes/container.route.ts b/projects/sandbox_server/src/routes/container.route.ts new file mode 100644 index 0000000000..d3ec7e1cfb --- /dev/null +++ b/projects/sandbox_server/src/routes/container.route.ts @@ -0,0 +1,188 @@ +import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; +import { + CreateContainerSchema, + ContainerInfoResponseSchema, + SuccessResponseSchema, + ErrorResponseSchema +} from '../schemas'; +import type { SealosClient } from '../clients'; + +// ==================== Route Definitions ==================== + +const createContainerRoute = createRoute({ + method: 'post', + path: '/containers', + tags: ['Container'], + summary: 'Create a new container', + request: { + body: { + content: { + 'application/json': { + schema: CreateContainerSchema + } + } + } + }, + responses: { + 200: { + content: { + 'application/json': { + schema: SuccessResponseSchema + } + }, + description: 'Container created successfully' + }, + 400: { + content: { + 'application/json': { + schema: ErrorResponseSchema + } + }, + description: 'Bad request' + } + } +}); + +const getContainerRoute = createRoute({ + method: 'get', + path: '/containers/{name}', + tags: ['Container'], + summary: 'Get container information', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }) + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ContainerInfoResponseSchema + } + }, + description: 'Container information' + }, + 404: { + content: { + 'application/json': { + schema: ErrorResponseSchema + } + }, + description: 'Container not found' + } + } +}); + +const pauseContainerRoute = createRoute({ + method: 'post', + path: '/containers/{name}/pause', + tags: ['Container'], + summary: 'Pause a running container', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }) + }, + responses: { + 200: { + content: { + 'application/json': { + schema: SuccessResponseSchema + } + }, + description: 'Container paused successfully' + } + } +}); + +const startContainerRoute = createRoute({ + method: 'post', + path: '/containers/{name}/start', + tags: ['Container'], + summary: 'Start a paused container', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }) + }, + responses: { + 200: { + content: { + 'application/json': { + schema: SuccessResponseSchema + } + }, + description: 'Container started successfully' + } + } +}); + +const deleteContainerRoute = createRoute({ + method: 'delete', + path: '/containers/{name}', + tags: ['Container'], + summary: 'Delete a container', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }) + }, + responses: { + 200: { + content: { + 'application/json': { + schema: SuccessResponseSchema + } + }, + description: 'Container deleted successfully' + } + } +}); + +// ==================== Controller Factory ==================== + +export const createContainerRoutes = (sealosClient: SealosClient) => { + const app = new OpenAPIHono(); + + // POST /containers - Create container + app.openapi(createContainerRoute, async (c) => { + const body = c.req.valid('json'); + await sealosClient.createContainer(body); + return c.json({ success: true as const }, 200); + }); + + // GET /containers/:name - Get container info + app.openapi(getContainerRoute, async (c) => { + const { name } = c.req.valid('param'); + const container = await sealosClient.getContainer(name); + + if (!container) { + return c.json({ success: false as const, message: 'Container not found' }, 404); + } + + return c.json({ success: true as const, data: container }, 200); + }); + + // POST /containers/:name/pause - Pause container + app.openapi(pauseContainerRoute, async (c) => { + const { name } = c.req.valid('param'); + await sealosClient.pauseContainer(name); + return c.json({ success: true as const }, 200); + }); + + // POST /containers/:name/start - Start container + app.openapi(startContainerRoute, async (c) => { + const { name } = c.req.valid('param'); + await sealosClient.resumeContainer(name); + return c.json({ success: true as const }, 200); + }); + + // DELETE /containers/:name - Delete container + app.openapi(deleteContainerRoute, async (c) => { + const { name } = c.req.valid('param'); + await sealosClient.deleteContainer(name); + return c.json({ success: true as const }, 200); + }); + + return app; +}; diff --git a/projects/sandbox_server/src/routes/index.ts b/projects/sandbox_server/src/routes/index.ts new file mode 100644 index 0000000000..e8da7dd8ca --- /dev/null +++ b/projects/sandbox_server/src/routes/index.ts @@ -0,0 +1,2 @@ +export { createContainerRoutes } from './container.route'; +export { createSandboxRoutes } from './sandbox.route'; diff --git a/projects/sandbox_server/src/routes/sandbox.route.ts b/projects/sandbox_server/src/routes/sandbox.route.ts new file mode 100644 index 0000000000..97a6360b66 --- /dev/null +++ b/projects/sandbox_server/src/routes/sandbox.route.ts @@ -0,0 +1,151 @@ +import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; +import { + ExecRequestSchema, + ExecResultResponseSchema, + HealthCheckResponseSchema, + ErrorResponseSchema +} from '../schemas'; +import { createSandboxClient, type SealosClient } from '../clients'; + +// ==================== Route Definitions ==================== + +const execRoute = createRoute({ + method: 'post', + path: '/sandbox/{name}/exec', + tags: ['Sandbox'], + summary: 'Execute a command in the sandbox', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }), + body: { + content: { + 'application/json': { + schema: ExecRequestSchema + } + } + } + }, + responses: { + 200: { + content: { + 'application/json': { + schema: ExecResultResponseSchema + } + }, + description: 'Command executed successfully' + }, + 400: { + content: { + 'application/json': { + schema: ErrorResponseSchema + } + }, + description: 'Bad request' + }, + 404: { + content: { + 'application/json': { + schema: ErrorResponseSchema + } + }, + description: 'Container not found' + } + } +}); + +const healthRoute = createRoute({ + method: 'get', + path: '/sandbox/{name}/health', + tags: ['Sandbox'], + summary: 'Check sandbox health', + request: { + params: z.object({ + name: z.string().openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) + }) + }, + responses: { + 200: { + content: { + 'application/json': { + schema: HealthCheckResponseSchema + } + }, + description: 'Health check result' + }, + 404: { + content: { + 'application/json': { + schema: ErrorResponseSchema + } + }, + description: 'Container not found' + } + } +}); + +// ==================== Controller Factory ==================== + +/** + * Factory function to create sandbox routes + * @param sealosClient - Sealos client to get container info for sandbox URL + */ +export const createSandboxRoutes = (sealosClient: SealosClient) => { + const app = new OpenAPIHono(); + + /** + * Get sandbox client by container name + * Retrieves container info to get the sandbox server URL + */ + const getSandboxClient = async (name: string) => { + const container = await sealosClient.getContainer(name); + if (!container || !container.server) { + throw new Error('Container not found or has no server info'); + } + + // Build sandbox URL from container server info + let baseUrl: string; + if (container.server.publicDomain && container.server.domain) { + baseUrl = `https://${container.server.publicDomain}.${container.server.domain}`; + } else { + baseUrl = `http://${container.server.serviceName}:${container.server.number}`; + } + + return createSandboxClient(baseUrl); + }; + + // POST /sandbox/:name/exec - Execute command + app.openapi(execRoute, async (c) => { + const { name } = c.req.valid('param'); + const body = c.req.valid('json'); + + try { + const sandboxClient = await getSandboxClient(name); + const result = await sandboxClient.exec(body); + return c.json({ success: true as const, data: result }, 200); + } catch (err) { + if (err instanceof Error && err.message.includes('not found')) { + return c.json({ success: false as const, message: 'Container not found' }, 404); + } + throw err; + } + }); + + // GET /sandbox/:name/health - Health check + app.openapi(healthRoute, async (c) => { + const { name } = c.req.valid('param'); + + try { + const sandboxClient = await getSandboxClient(name); + const healthy = await sandboxClient.isHealthy(); + return c.json({ success: true as const, healthy }, 200); + } catch (err) { + if (err instanceof Error && err.message.includes('not found')) { + return c.json({ success: false as const, message: 'Container not found' }, 404); + } + throw err; + } + }); + + return app; +}; diff --git a/projects/sandbox_server/src/schemas/common.schema.d.ts b/projects/sandbox_server/src/schemas/common.schema.d.ts new file mode 100644 index 0000000000..5a058065f0 --- /dev/null +++ b/projects/sandbox_server/src/schemas/common.schema.d.ts @@ -0,0 +1,23 @@ +import type { z } from '@hono/zod-openapi'; +export declare const SuccessResponseSchema: z.ZodObject< + { + success: z.ZodLiteral; + }, + z.core.$strip +>; +export type SuccessResponse = z.infer; +export declare const ErrorResponseSchema: z.ZodObject< + { + success: z.ZodLiteral; + message: z.ZodString; + }, + z.core.$strip +>; +export type ErrorResponse = z.infer; +export declare const NameParamSchema: z.ZodObject< + { + name: z.ZodString; + }, + z.core.$strip +>; +export type NameParam = z.infer; diff --git a/projects/sandbox_server/src/schemas/common.schema.ts b/projects/sandbox_server/src/schemas/common.schema.ts new file mode 100644 index 0000000000..239f304654 --- /dev/null +++ b/projects/sandbox_server/src/schemas/common.schema.ts @@ -0,0 +1,22 @@ +import { z } from '@hono/zod-openapi'; + +// Common response wrapper +export const SuccessResponseSchema = z.object({ + success: z.literal(true) +}); +export type SuccessResponse = z.infer; + +export const ErrorResponseSchema = z.object({ + success: z.literal(false), + message: z.string() +}); +export type ErrorResponse = z.infer; + +// Path parameter for container/sandbox name +export const NameParamSchema = z.object({ + name: z + .string() + .min(1) + .openapi({ param: { name: 'name', in: 'path' }, example: 'my-container' }) +}); +export type NameParam = z.infer; diff --git a/projects/sandbox_server/src/schemas/container.schema.d.ts b/projects/sandbox_server/src/schemas/container.schema.d.ts new file mode 100644 index 0000000000..24b16c9525 --- /dev/null +++ b/projects/sandbox_server/src/schemas/container.schema.d.ts @@ -0,0 +1,150 @@ +import type { z } from '@hono/zod-openapi'; +export declare const CreateContainerSchema: z.ZodObject< + { + name: z.ZodString; + }, + z.core.$strip +>; +export type CreateContainerInput = z.infer; +export declare const ContainerStatusSchema: z.ZodObject< + { + state: z.ZodEnum<{ + Running: 'Running'; + Creating: 'Creating'; + Paused: 'Paused'; + Error: 'Error'; + Unknown: 'Unknown'; + }>; + replicas: z.ZodOptional; + availableReplicas: z.ZodOptional; + }, + z.core.$strip +>; +export type ContainerStatus = z.infer; +export declare const ContainerServerSchema: z.ZodObject< + { + serviceName: z.ZodString; + number: z.ZodNumber; + publicDomain: z.ZodOptional; + domain: z.ZodOptional; + }, + z.core.$strip +>; +export type ContainerServer = z.infer; +export declare const ContainerInfoSchema: z.ZodObject< + { + name: z.ZodString; + image: z.ZodObject< + { + imageName: z.ZodString; + }, + z.core.$strip + >; + status: z.ZodObject< + { + state: z.ZodEnum<{ + Running: 'Running'; + Creating: 'Creating'; + Paused: 'Paused'; + Error: 'Error'; + Unknown: 'Unknown'; + }>; + replicas: z.ZodOptional; + availableReplicas: z.ZodOptional; + }, + z.core.$strip + >; + server: z.ZodOptional< + z.ZodObject< + { + serviceName: z.ZodString; + number: z.ZodNumber; + publicDomain: z.ZodOptional; + domain: z.ZodOptional; + }, + z.core.$strip + > + >; + createdAt: z.ZodOptional; + }, + z.core.$strip +>; +export type ContainerInfo = z.infer; +export declare const ContainerInfoResponseSchema: z.ZodObject< + { + success: z.ZodLiteral; + data: z.ZodObject< + { + name: z.ZodString; + image: z.ZodObject< + { + imageName: z.ZodString; + }, + z.core.$strip + >; + status: z.ZodObject< + { + state: z.ZodEnum<{ + Running: 'Running'; + Creating: 'Creating'; + Paused: 'Paused'; + Error: 'Error'; + Unknown: 'Unknown'; + }>; + replicas: z.ZodOptional; + availableReplicas: z.ZodOptional; + }, + z.core.$strip + >; + server: z.ZodOptional< + z.ZodObject< + { + serviceName: z.ZodString; + number: z.ZodNumber; + publicDomain: z.ZodOptional; + domain: z.ZodOptional; + }, + z.core.$strip + > + >; + createdAt: z.ZodOptional; + }, + z.core.$strip + >; + }, + z.core.$strip +>; +export type ContainerInfoResponse = z.infer; +export declare const SealosContainerResponseSchema: z.ZodObject< + { + name: z.ZodString; + image: z.ZodObject< + { + imageName: z.ZodString; + }, + z.core.$strip + >; + createTime: z.ZodOptional; + status: z.ZodObject< + { + replicas: z.ZodCoercedNumber; + availableReplicas: z.ZodCoercedNumber; + isPause: z.ZodCoercedBoolean; + }, + z.core.$strip + >; + ports: z.ZodArray< + z.ZodObject< + { + serviceName: z.ZodString; + number: z.ZodCoercedNumber; + publicDomain: z.ZodOptional; + domain: z.ZodOptional; + }, + z.core.$strip + > + >; + }, + z.core.$strip +>; +export type SealosContainerResponse = z.infer; diff --git a/projects/sandbox_server/src/schemas/container.schema.ts b/projects/sandbox_server/src/schemas/container.schema.ts new file mode 100644 index 0000000000..ca6907f195 --- /dev/null +++ b/projects/sandbox_server/src/schemas/container.schema.ts @@ -0,0 +1,66 @@ +import { z } from '@hono/zod-openapi'; + +// ==================== Request Schemas ==================== + +export const CreateContainerSchema = z.object({ + name: z.string().min(1).openapi({ example: 'my-container' }) +}); +export type CreateContainerInput = z.infer; + +// ==================== Response Schemas ==================== + +export const ContainerStatusSchema = z.object({ + state: z.enum(['Running', 'Creating', 'Paused', 'Error', 'Unknown']), + replicas: z.number().optional(), + availableReplicas: z.number().optional() +}); +export type ContainerStatus = z.infer; + +export const ContainerServerSchema = z.object({ + serviceName: z.string(), + number: z.number(), + publicDomain: z.string().optional(), + domain: z.string().optional() +}); +export type ContainerServer = z.infer; + +export const ContainerInfoSchema = z.object({ + name: z.string(), + image: z.object({ + imageName: z.string() + }), + status: ContainerStatusSchema, + server: ContainerServerSchema.optional(), + createdAt: z.string().optional() +}); +export type ContainerInfo = z.infer; + +export const ContainerInfoResponseSchema = z.object({ + success: z.literal(true), + data: ContainerInfoSchema +}); +export type ContainerInfoResponse = z.infer; + +// ==================== Sealos API Response Schemas ==================== + +export const SealosContainerResponseSchema = z.object({ + name: z.string(), + image: z.object({ + imageName: z.string() + }), + createTime: z.string().optional(), + status: z.object({ + replicas: z.coerce.number(), + availableReplicas: z.coerce.number(), + isPause: z.coerce.boolean() + }), + ports: z.array( + z.object({ + serviceName: z.string(), + number: z.coerce.number(), + publicDomain: z.string().optional(), + domain: z.string().optional() + }) + ) +}); +export type SealosContainerResponse = z.infer; diff --git a/projects/sandbox_server/src/schemas/index.d.ts b/projects/sandbox_server/src/schemas/index.d.ts new file mode 100644 index 0000000000..0956a88ba9 --- /dev/null +++ b/projects/sandbox_server/src/schemas/index.d.ts @@ -0,0 +1,3 @@ +export * from './common.schema'; +export * from './container.schema'; +export * from './sandbox.schema'; diff --git a/projects/sandbox_server/src/schemas/index.ts b/projects/sandbox_server/src/schemas/index.ts new file mode 100644 index 0000000000..0956a88ba9 --- /dev/null +++ b/projects/sandbox_server/src/schemas/index.ts @@ -0,0 +1,3 @@ +export * from './common.schema'; +export * from './container.schema'; +export * from './sandbox.schema'; diff --git a/projects/sandbox_server/src/schemas/sandbox.schema.d.ts b/projects/sandbox_server/src/schemas/sandbox.schema.d.ts new file mode 100644 index 0000000000..0ad6b6bce1 --- /dev/null +++ b/projects/sandbox_server/src/schemas/sandbox.schema.d.ts @@ -0,0 +1,55 @@ +import type { z } from '@hono/zod-openapi'; +export declare const ExecRequestSchema: z.ZodObject< + { + command: z.ZodString; + cwd: z.ZodOptional; + }, + z.core.$strip +>; +export type ExecRequest = z.infer; +export declare const HealthResponseSchema: z.ZodObject< + { + status: z.ZodString; + timestamp: z.ZodOptional; + }, + z.core.$strip +>; +export type HealthResponse = z.infer; +export declare const ExecResponseSchema: z.ZodObject< + { + success: z.ZodBoolean; + stdout: z.ZodString; + stderr: z.ZodString; + exitCode: z.ZodNumber; + cwd: z.ZodOptional; + error: z.ZodOptional; + }, + z.core.$strip +>; +export type ExecResponse = z.infer; +export declare const HealthCheckResponseSchema: z.ZodObject< + { + success: z.ZodLiteral; + healthy: z.ZodBoolean; + }, + z.core.$strip +>; +export type HealthCheckResponse = z.infer; +export declare const ExecResultResponseSchema: z.ZodObject< + { + success: z.ZodLiteral; + data: z.ZodObject< + { + success: z.ZodBoolean; + stdout: z.ZodString; + stderr: z.ZodString; + exitCode: z.ZodNumber; + cwd: z.ZodOptional; + error: z.ZodOptional; + }, + z.core.$strip + >; + }, + z.core.$strip +>; +export type ExecResultResponse = z.infer; diff --git a/projects/sandbox_server/src/schemas/sandbox.schema.ts b/projects/sandbox_server/src/schemas/sandbox.schema.ts new file mode 100644 index 0000000000..7ada3892fa --- /dev/null +++ b/projects/sandbox_server/src/schemas/sandbox.schema.ts @@ -0,0 +1,39 @@ +import { z } from '@hono/zod-openapi'; + +// ==================== Request Schemas ==================== + +export const ExecRequestSchema = z.object({ + command: z.string().min(1).openapi({ example: 'ls -la' }), + cwd: z.string().optional().openapi({ example: '/app/sandbox' }) +}); +export type ExecRequest = z.infer; + +// ==================== Response Schemas ==================== + +export const HealthResponseSchema = z.object({ + status: z.string(), + timestamp: z.string().optional() +}); +export type HealthResponse = z.infer; + +export const ExecResponseSchema = z.object({ + success: z.boolean(), + stdout: z.string(), + stderr: z.string(), + exitCode: z.number(), + cwd: z.string().optional(), + error: z.string().optional() +}); +export type ExecResponse = z.infer; + +export const HealthCheckResponseSchema = z.object({ + success: z.literal(true), + healthy: z.boolean() +}); +export type HealthCheckResponse = z.infer; + +export const ExecResultResponseSchema = z.object({ + success: z.literal(true), + data: ExecResponseSchema +}); +export type ExecResultResponse = z.infer; diff --git a/projects/sandbox_server/src/utils/index.ts b/projects/sandbox_server/src/utils/index.ts new file mode 100644 index 0000000000..16b4cac812 --- /dev/null +++ b/projects/sandbox_server/src/utils/index.ts @@ -0,0 +1 @@ +export { logger } from './logger'; diff --git a/projects/sandbox_server/src/utils/logger.ts b/projects/sandbox_server/src/utils/logger.ts new file mode 100644 index 0000000000..c2d9cdebb1 --- /dev/null +++ b/projects/sandbox_server/src/utils/logger.ts @@ -0,0 +1,154 @@ +/** + * Logger utility for sandbox server + * Provides structured, formatted logging with color support + */ + +// ANSI color codes +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + + // Foreground colors + black: '\x1b[30m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + gray: '\x1b[90m' +}; + +type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'; + +const levelColors: Record = { + DEBUG: colors.gray, + INFO: colors.green, + WARN: colors.yellow, + ERROR: colors.red +}; + +const methodColors: Record = { + GET: colors.cyan, + POST: colors.green, + PUT: colors.yellow, + PATCH: colors.yellow, + DELETE: colors.red +}; + +/** + * Format timestamp as HH:mm:ss.SSS + */ +function formatTime(date: Date): string { + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const seconds = date.getSeconds().toString().padStart(2, '0'); + const ms = date.getMilliseconds().toString().padStart(3, '0'); + return `${hours}:${minutes}:${seconds}.${ms}`; +} + +/** + * Format duration with appropriate unit + */ +function formatDuration(ms: number): string { + if (ms < 1000) { + return `${ms}ms`; + } + return `${(ms / 1000).toFixed(2)}s`; +} + +/** + * Get color for HTTP status code + */ +function getStatusColor(status: number): string { + if (status >= 500) return colors.red; + if (status >= 400) return colors.yellow; + if (status >= 300) return colors.cyan; + if (status >= 200) return colors.green; + return colors.white; +} + +/** + * Core log function + */ +function log( + level: LogLevel, + category: string, + message: string, + meta?: Record +): void { + const now = new Date(); + const time = formatTime(now); + const levelColor = levelColors[level]; + const levelStr = level.padEnd(5); + + let output = `${colors.dim}${time}${colors.reset} ${levelColor}${levelStr}${colors.reset} ${colors.bright}[${category}]${colors.reset} ${message}`; + + if (meta && Object.keys(meta).length > 0) { + const metaStr = Object.entries(meta) + .map(([k, v]) => `${colors.dim}${k}=${colors.reset}${v}`) + .join(' '); + output += ` ${metaStr}`; + } + + console.log(output); +} + +/** + * Logger interface + */ +export const logger = { + debug: (category: string, message: string, meta?: Record) => + log('DEBUG', category, message, meta), + + info: (category: string, message: string, meta?: Record) => + log('INFO', category, message, meta), + + warn: (category: string, message: string, meta?: Record) => + log('WARN', category, message, meta), + + error: (category: string, message: string, meta?: Record) => + log('ERROR', category, message, meta), + + /** + * Log HTTP request start + */ + httpRequest: (method: string, path: string) => { + const methodColor = methodColors[method] || colors.white; + const methodStr = method.padEnd(6); + log( + 'INFO', + 'HTTP', + `${colors.bright}-->${colors.reset} ${methodColor}${methodStr}${colors.reset} ${path}` + ); + }, + + /** + * Log HTTP response + */ + httpResponse: ( + method: string, + path: string, + status: number, + duration: number, + error?: string + ) => { + const methodColor = methodColors[method] || colors.white; + const statusColor = getStatusColor(status); + const methodStr = method.padEnd(6); + const durationStr = formatDuration(duration); + const level: LogLevel = status >= 400 ? 'ERROR' : 'INFO'; + + let message = `${colors.bright}<--${colors.reset} ${methodColor}${methodStr}${colors.reset} ${path} ${statusColor}${status}${colors.reset} ${colors.dim}${durationStr}${colors.reset}`; + + if (error) { + message += ` ${colors.red}${error}${colors.reset}`; + } + + log(level, 'HTTP', message); + } +}; + +export default logger; diff --git a/projects/sandbox_server/test/.env.test.template b/projects/sandbox_server/test/.env.test.template new file mode 100644 index 0000000000..f370ab61f4 --- /dev/null +++ b/projects/sandbox_server/test/.env.test.template @@ -0,0 +1,16 @@ +# Sealos Configuration +SEALOS_BASE_URL=https://applaunchpad.hzh.sealos.run +SEALOS_KC= + +# Container Configuration (fixed for all containers) +CONTAINER_IMAGE=hub.hzh.sealos.run/ns-4gabgrbc/agent-sandbox:v0.0.7 +CONTAINER_PORT=8080 +CONTAINER_CPU=0.5 +CONTAINER_MEMORY=1 +# Entrypoint format: JSON array like '["/bin/bash","-c","script.sh"]' or plain command +CONTAINER_ENTRYPOINT='["/bin/bash -c","/home/devbox/project/entrypoint.sh prod"]' +# Whether to expose container to public domain +CONTAINER_EXPOSES_PUBLIC_DOMAIN=true + +SDK_SERVER_URL=http://localhost:3000 +SDK_TOKEN=test-token \ No newline at end of file diff --git a/projects/sandbox_server/test/.gitignore b/projects/sandbox_server/test/.gitignore new file mode 100644 index 0000000000..c3efd10394 --- /dev/null +++ b/projects/sandbox_server/test/.gitignore @@ -0,0 +1,2 @@ +# Ignore local test environment files +.env.test.local diff --git a/projects/sandbox_server/test/README.md b/projects/sandbox_server/test/README.md new file mode 100644 index 0000000000..9e79612804 --- /dev/null +++ b/projects/sandbox_server/test/README.md @@ -0,0 +1,147 @@ +# 测试说明 + +## 测试类型 + +### 1. 单元测试 (`test/app.test.ts`) + +基础的 HTTP 端点测试,无需外部依赖。 + +```bash +bun run test +``` + +测试内容: +- `/health` 健康检查 +- `/openapi` OpenAPI 文档 +- Bearer Token 鉴权 + +### 2. 集成测试 (`test/integration/`) + +测试与真实 Sealos API 的集成,需要配置环境变量。 + +#### 配置步骤 + +1. 复制环境变量模板: +```bash +cp test/.env.test.template test/.env.test.local +``` + +2. 编辑 `test/.env.test.local`,填写真实配置: +```env +# 服务器配置 +PORT=3000 +TOKEN=your-api-token + +# Sealos 配置(使用测试环境)- 提供 SEALOS_KC 后集成测试自动运行 +SEALOS_BASE_URL=https://your-sealos-api.com +SEALOS_KC=your-kubeconfig-token + +# 测试镜像 +TEST_IMAGE=nginx:alpine +TEST_SANDBOX_IMAGE=ghcr.io/your-org/sandbox-server:latest +``` + +3. 运行测试(集成测试会自动运行,如果配置了 SEALOS_KC): +```bash +bun run test +``` + +#### 测试内容 + +**容器生命周期测试** (`test/integration/container.test.ts`) +- ✅ 创建容器 +- ✅ 获取容器信息 +- ✅ 暂停容器 +- ✅ 启动容器 +- ✅ 删除容器 +- ✅ 幂等性测试(重复创建、删除不存在的容器) + +**沙盒操作测试** (`test/integration/sandbox.test.ts`) +- ✅ 健康检查 +- ✅ 执行简单命令 +- ✅ 捕获 stdout/stderr +- ✅ 工作目录设置 +- ✅ 管道命令 +- ✅ 多行脚本 +- ✅ 错误处理 + +## 运行测试 + +### 仅运行单元测试 +```bash +bun run test:run +``` + +### 运行所有测试(包括集成测试) +```bash +# 确保已配置 .env.test.local 并提供 SEALOS_KC +bun run test +``` + +### 运行特定测试文件 +```bash +bun run test test/integration/container.test.ts +``` + +### 查看测试覆盖率 +```bash +bun run test -- --coverage +``` + +## 注意事项 + +### 集成测试注意事项 + +1. **使用测试环境** + - 不要在生产环境运行集成测试 + - 确保有足够的资源配额 + +2. **清理资源** + - 测试会自动清理创建的容器 + - 如果测试中断,可能需要手动清理 + +3. **网络要求** + - 需要能访问 Sealos API + - 需要能拉取测试镜像 + +4. **超时设置** + - 容器启动可能需要 60-90 秒 + - 某些测试设置了较长的超时时间 + +### 环境变量优先级 + +1. `.env.test.local` - 本地测试配置(不提交到 git) +2. 默认值 - 使用 mock 数据 + +### 跳过集成测试 + +如果 `SEALOS_KC` 未提供,集成测试会自动跳过。这样可以避免意外运行集成测试。 + +## 故障排查 + +### 测试失败:认证错误 +- 检查 `SEALOS_KC` 是否有效 +- 确认 token 有足够的权限 + +### 测试失败:超时 +- 增加测试超时时间 +- 检查网络连接 +- 确认 Sealos 服务正常 + +### 测试失败:容器创建失败 +- 检查资源配额 +- 确认镜像可访问 +- 查看 Sealos 日志 + +## 示例 + +### 运行完整测试流程 + +```bash +# 1. 配置环境变量 +cp test/.env.test.template test/.env.test.local +# 编辑 test/.env.test.local + +# 2. 运行测试(集成测试会自动运行) +bun run test +``` diff --git a/projects/sandbox_server/test/app.test.ts b/projects/sandbox_server/test/app.test.ts new file mode 100644 index 0000000000..bf5501219d --- /dev/null +++ b/projects/sandbox_server/test/app.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { app } from '../src/index'; + +describe('App', () => { + describe('GET /health', () => { + it('should return health status', async () => { + const res = await app.request('/health'); + expect(res.status).toBe(200); + + const data = (await res.json()) as { status: string; timestamp: string }; + expect(data.status).toBe('ok'); + expect(data.timestamp).toBeDefined(); + }); + }); + + describe('GET /openapi', () => { + it('should return OpenAPI document', async () => { + const res = await app.request('/openapi'); + expect(res.status).toBe(200); + + const data = (await res.json()) as { openapi: string; info: { title: string } }; + expect(data.openapi).toBe('3.0.0'); + expect(data.info.title).toBe('Sandbox Server API'); + }); + }); + + describe('Protected routes', () => { + it('should return 401 without authorization header', async () => { + const res = await app.request('/v1/containers/test'); + expect(res.status).toBe(401); + }); + + it('should return 401 with invalid token', async () => { + const res = await app.request('/v1/containers/test', { + headers: { + Authorization: 'Bearer invalid-token' + } + }); + expect(res.status).toBe(401); + }); + }); +}); diff --git a/projects/sandbox_server/test/integration/container.test.ts b/projects/sandbox_server/test/integration/container.test.ts new file mode 100644 index 0000000000..7907df8296 --- /dev/null +++ b/projects/sandbox_server/test/integration/container.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { createSealosClient } from '../../src/clients'; +import type { SealosClient } from '../../src/clients'; +import { env, containerConfig } from '../../src/env'; + +/** + * Integration tests for Container lifecycle management. + * + * Tests run only when SEALOS_KC is provided in .env.test.local + * + * Required environment variables: + * - SEALOS_BASE_URL: Sealos API base URL + * - SEALOS_KC: Sealos kubeconfig token + * - CONTAINER_IMAGE: Docker image for container + * - CONTAINER_CPU: CPU resource + * - CONTAINER_MEMORY: Memory resource + */ + +const sealosKc = env.SEALOS_KC; + +describe.skipIf(!sealosKc)('Container Integration Tests', () => { + // Generate unique container name for test isolation + const testContainerName = `test-container-${Math.random().toString(36).substring(2, 8)}`; + + let sealosClient: SealosClient; + + beforeAll(() => { + sealosClient = createSealosClient(); + }); + + afterAll(async () => { + // Cleanup: ensure container is deleted after tests + try { + await sealosClient.deleteContainer(testContainerName); + } catch { + // Ignore cleanup errors + } + }); + + describe('Container Lifecycle', () => { + it('should return null when getting non-existent container', async () => { + const info = await sealosClient.getContainer(testContainerName); + expect(info).toBeNull(); + }); + + it('should create a new container', async () => { + await sealosClient.createContainer({ name: testContainerName }); + + // Verify container was created by getting its info + const info = await sealosClient.getContainer(testContainerName); + expect(info).not.toBeNull(); + expect(info!.name).toBe(testContainerName); + // Image should match the configured image from environment + expect(info!.image.imageName).toContain(containerConfig.image.split(':')[0]); + }); + + it('should get container information', async () => { + const info = await sealosClient.getContainer(testContainerName); + + expect(info).not.toBeNull(); + expect(info!.name).toBe(testContainerName); + expect(info!.image).toBeDefined(); + expect(info!.status).toBeDefined(); + expect(['Creating', 'Running', 'Paused', 'Error', 'Unknown']).toContain(info!.status.state); + }); + + it('should wait for container to be running', async () => { + // Wait for container to be ready + await waitForContainerState(sealosClient, testContainerName, ['Running'], 20000); + + const info = await sealosClient.getContainer(testContainerName); + expect(info!.status.state).toBe('Running'); + }, 30000); + + it('should pause a running container', async () => { + await sealosClient.pauseContainer(testContainerName); + + // Wait and verify paused state + await waitForContainerState(sealosClient, testContainerName, ['Paused'], 30000); + + const info = await sealosClient.getContainer(testContainerName); + expect(info!.status.state).toBe('Paused'); + }, 60000); + + it('should start/resume a paused container', async () => { + await sealosClient.resumeContainer(testContainerName); + + // Wait and verify running state + await waitForContainerState(sealosClient, testContainerName, ['Running'], 20000); + + const info = await sealosClient.getContainer(testContainerName); + expect(info!.status.state).toBe('Running'); + }, 30000); + + it('should delete the container', async () => { + await sealosClient.deleteContainer(testContainerName); + + // Verify container no longer exists + await sleep(2000); // Give it time to delete + const info = await sealosClient.getContainer(testContainerName); + expect(info).toBeNull(); + }); + }); + + describe('Idempotent Operations', () => { + const idempotentTestName = `idempotent-${Math.random().toString(36).substring(2, 8)}`; + + afterAll(async () => { + try { + await sealosClient.deleteContainer(idempotentTestName); + } catch { + // Ignore + } + }); + + it('should handle creating an already existing container', async () => { + // Create container + await sealosClient.createContainer({ name: idempotentTestName }); + + // Creating again should not throw (Sealos returns existing container) + await expect( + sealosClient.createContainer({ name: idempotentTestName }) + ).resolves.not.toThrow(); + + // Cleanup + await sealosClient.deleteContainer(idempotentTestName); + }, 60000); + + it('should handle deleting a non-existent container', async () => { + const nonExistentName = `non-existent-${Math.random().toString(36).substring(2, 8)}`; + + // Deleting non-existent container should not throw + await expect(sealosClient.deleteContainer(nonExistentName)).resolves.not.toThrow(); + }); + }); +}); + +/** + * Helper function to wait for container to reach expected state + */ +async function waitForContainerState( + client: SealosClient, + name: string, + expectedStates: string[], + timeoutMs: number = 30000 +): Promise { + const startTime = Date.now(); + const pollInterval = 2000; + + while (Date.now() - startTime < timeoutMs) { + const info = await client.getContainer(name); + + if (info && expectedStates.includes(info.status.state)) { + return; + } + + await sleep(pollInterval); + } + + throw new Error( + `Timeout waiting for container state. Expected: ${expectedStates.join(' or ')}, timeout: ${timeoutMs}ms` + ); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/projects/sandbox_server/test/integration/sandbox.test.ts b/projects/sandbox_server/test/integration/sandbox.test.ts new file mode 100644 index 0000000000..a45df5ab6e --- /dev/null +++ b/projects/sandbox_server/test/integration/sandbox.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { createSealosClient, createSandboxClient } from '../../src/clients'; +import type { SealosClient, SandboxClient } from '../../src/clients'; +import { env } from '../../src/env'; + +/** + * Integration tests for Sandbox operations (exec and health check). + * + * Tests run only when SEALOS_KC is provided in .env.test.local + * + * Required environment variables: + * - SEALOS_BASE_URL: Sealos API base URL + * - SEALOS_KC: Sealos kubeconfig token + * - SEALOS_IMAGE: Docker image with sandbox server (must have /health and /exec endpoints) + */ + +describe.skipIf(!env.SEALOS_KC)('Sandbox Integration Tests', () => { + // Generate unique container name for test isolation + const testContainerName = `sandbox-test-${Math.random().toString(36).substring(2, 8)}`; + + let sealosClient: SealosClient; + let sandboxClient: SandboxClient; + + beforeAll(async () => { + sealosClient = createSealosClient(); + + // Create container with sandbox server + await sealosClient.createContainer({ name: testContainerName }); + + // Wait for container to be running + await waitForContainerState(sealosClient, testContainerName, ['Running'], 90000); + + // Get container info to build sandbox URL + const containerInfo = await sealosClient.getContainer(testContainerName); + if (!containerInfo || !containerInfo.server) { + throw new Error('Failed to get container server info'); + } + + // Build sandbox URL + let sandboxUrl: string; + if (containerInfo.server.publicDomain && containerInfo.server.domain) { + sandboxUrl = `https://${containerInfo.server.publicDomain}.${containerInfo.server.domain}`; + } else { + sandboxUrl = `http://${containerInfo.server.serviceName}:${containerInfo.server.number}`; + } + + sandboxClient = createSandboxClient(sandboxUrl); + + // Give sandbox server time to start + await sleep(5000); + }); + + afterAll(async () => { + // Cleanup: delete test container + try { + await sealosClient.deleteContainer(testContainerName); + } catch { + // Ignore cleanup errors + } + }); + + describe('Health Check', () => { + it('should check sandbox health', async () => { + const healthy = await sandboxClient.isHealthy(); + expect(typeof healthy).toBe('boolean'); + }); + + it('should get health response', async () => { + const health = await sandboxClient.health(); + expect(health).toBeDefined(); + expect(health.status).toBeDefined(); + }); + }); + + describe('Command Execution', () => { + it('should execute a simple echo command', async () => { + const result = await sandboxClient.exec({ + command: 'echo "Hello, World!"' + }); + + expect(result.stdout.trim()).toBe('Hello, World!'); + expect(result.stderr).toBe(''); + expect(result.exitCode).toBe(0); + }); + + it('should return correct exit code for successful command', async () => { + const result = await sandboxClient.exec({ + command: 'true' + }); + + expect(result.exitCode).toBe(0); + }); + + it('should return non-zero exit code for failed command', async () => { + const result = await sandboxClient.exec({ + command: 'false' + }); + + expect(result.exitCode).not.toBe(0); + }); + + it('should capture stderr output', async () => { + const result = await sandboxClient.exec({ + command: 'echo "error message" >&2' + }); + + expect(result.stderr).toContain('error message'); + expect(result.exitCode).toBe(0); + }); + + it('should capture both stdout and stderr', async () => { + const result = await sandboxClient.exec({ + command: 'echo "out" && echo "err" >&2' + }); + + expect(result.stdout).toContain('out'); + expect(result.stderr).toContain('err'); + }); + + it('should execute command with working directory', async () => { + const result = await sandboxClient.exec({ + command: 'pwd', + cwd: '/tmp' + }); + + expect(result.stdout.trim()).toBe('/tmp'); + expect(result.exitCode).toBe(0); + }); + + it('should execute piped commands', async () => { + const result = await sandboxClient.exec({ + command: 'echo "hello world" | tr "a-z" "A-Z"' + }); + + expect(result.stdout.trim()).toBe('HELLO WORLD'); + expect(result.exitCode).toBe(0); + }); + + it('should handle command with special characters', async () => { + const result = await sandboxClient.exec({ + command: 'echo "test$var\'quote\\"double"' + }); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toBeDefined(); + }); + + it('should execute multi-line script', async () => { + const script = ` + count=0 + for i in 1 2 3; do + count=$((count + 1)) + done + echo $count + `; + const result = await sandboxClient.exec({ + command: script + }); + + expect(result.stdout.trim()).toBe('3'); + expect(result.exitCode).toBe(0); + }); + + it('should handle command that does not exist', async () => { + const result = await sandboxClient.exec({ + command: 'nonexistent_command_12345' + }); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr.length > 0 || result.stdout.length > 0).toBe(true); + }); + + it('should handle empty command output', async () => { + const result = await sandboxClient.exec({ + command: 'true' + }); + + expect(result.stdout).toBe(''); + expect(result.exitCode).toBe(0); + }); + + it('should handle large output', async () => { + const result = await sandboxClient.exec({ + command: 'seq 1 100' + }); + + expect(result.stdout).toContain('1\n'); + expect(result.stdout).toMatch(/100(\n|$)/); + expect(result.exitCode).toBe(0); + }); + + it('should preserve environment in command', async () => { + const result = await sandboxClient.exec({ + command: 'export MY_VAR="test123" && echo $MY_VAR' + }); + + expect(result.stdout.trim()).toBe('test123'); + }); + }); + + describe('Error Handling', () => { + it('should handle connection errors gracefully', async () => { + const invalidClient = createSandboxClient('http://invalid-url-12345.com'); + + await expect(invalidClient.health()).rejects.toThrow(); + }); + + it('should handle command timeout', async () => { + // Execute a command that completes quickly to test proper execution + const result = await sandboxClient.exec({ + command: 'sleep 0.1' + }); + + expect(result).toBeDefined(); + expect(result.exitCode).toBe(0); + }); + }); +}); + +/** + * Helper function to wait for container to reach expected state + */ +async function waitForContainerState( + client: SealosClient, + name: string, + expectedStates: string[], + timeoutMs: number = 30000 +): Promise { + const startTime = Date.now(); + const pollInterval = 2000; + + while (Date.now() - startTime < timeoutMs) { + try { + const info = await client.getContainer(name); + + if (info && expectedStates.includes(info.status.state)) { + return; + } + } catch { + // Ignore errors during polling + } + + await sleep(pollInterval); + } + + throw new Error( + `Timeout waiting for container state. Expected: ${expectedStates.join(' or ')}, timeout: ${timeoutMs}ms` + ); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/projects/sandbox_server/test/integration/sdk.test.ts b/projects/sandbox_server/test/integration/sdk.test.ts new file mode 100644 index 0000000000..533267962d --- /dev/null +++ b/projects/sandbox_server/test/integration/sdk.test.ts @@ -0,0 +1,488 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nock from 'nock'; +import { createSDK, createSDKFromConfig } from '../../sdk/index'; +import type { ExecRequest } from '../../src/schemas'; + +describe('Sandbox Server SDK', () => { + const baseUrl = 'http://localhost:3000'; + const token = 'test-token'; + + beforeEach(() => { + // Clean all HTTP mocks before each test + nock.cleanAll(); + }); + + afterEach(() => { + // Verify that all expected HTTP calls were made + if (!nock.isDone()) { + const pendingMocks = nock.pendingMocks(); + if (pendingMocks.length > 0) { + console.warn('Pending mocks:', pendingMocks); + } + } + nock.cleanAll(); + }); + + describe('SDK Initialization', () => { + it('should create SDK with correct structure', () => { + const sdk = createSDK(baseUrl, token); + + expect(sdk.container).toBeDefined(); + expect(sdk.sandbox).toBeDefined(); + expect(sdk.container.create).toBeInstanceOf(Function); + expect(sdk.container.get).toBeInstanceOf(Function); + expect(sdk.container.pause).toBeInstanceOf(Function); + expect(sdk.container.start).toBeInstanceOf(Function); + expect(sdk.container.delete).toBeInstanceOf(Function); + expect(sdk.sandbox.exec).toBeInstanceOf(Function); + expect(sdk.sandbox.health).toBeInstanceOf(Function); + }); + + it('should create SDK from config object', () => { + const config = { baseUrl, token }; + const sdk = createSDKFromConfig(config); + + expect(sdk.container).toBeDefined(); + expect(sdk.sandbox).toBeDefined(); + }); + }); + + describe('ContainerSDK', () => { + let sdk: ReturnType; + + beforeEach(() => { + sdk = createSDK(baseUrl, token); + }); + + describe('create', () => { + it('should send POST request to /v1/containers with name only', async () => { + const params = { name: 'test-container' }; + + const scope = nock(baseUrl) + .post('/v1/containers', (body) => { + // Verify request body only contains name + expect(body).toMatchObject(params); + return true; + }) + .matchHeader('authorization', `Bearer ${token}`) + .matchHeader('content-type', 'application/json') + .reply(200, { success: true }); + + await sdk.container.create(params); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('get', () => { + it('should send GET request to /v1/containers/:name with encoded name', async () => { + const containerName = 'test-container'; + const mockResponse = { + success: true, + data: { + name: containerName, + image: { imageName: 'node:18' }, + status: { state: 'Running' } + } + }; + + const scope = nock(baseUrl) + .get(`/v1/containers/${encodeURIComponent(containerName)}`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + const result = await sdk.container.get(containerName); + + expect(scope.isDone()).toBe(true); + expect(result).toBeDefined(); + expect(result?.name).toBe(containerName); + }); + + it('should handle special characters in container name', async () => { + const containerName = 'test-container@123'; + const mockResponse = { + success: true, + data: { + name: containerName, + image: { imageName: 'node:18' }, + status: { state: 'Running' } + } + }; + + const scope = nock(baseUrl) + .get(`/v1/containers/${encodeURIComponent(containerName)}`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + await sdk.container.get(containerName); + + expect(scope.isDone()).toBe(true); + }); + + it('should return null for 404 error', async () => { + const containerName = 'non-existent'; + + const scope = nock(baseUrl) + .get(`/v1/containers/${encodeURIComponent(containerName)}`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(404, { success: false, message: 'Not found' }); + + const result = await sdk.container.get(containerName); + + expect(scope.isDone()).toBe(true); + expect(result).toBeNull(); + }); + }); + + describe('pause', () => { + it('should send POST request to /v1/containers/:name/pause', async () => { + const containerName = 'test-container'; + + const scope = nock(baseUrl) + .post(`/v1/containers/${encodeURIComponent(containerName)}/pause`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, { success: true }); + + await sdk.container.pause(containerName); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('start', () => { + it('should send POST request to /v1/containers/:name/start', async () => { + const containerName = 'test-container'; + + const scope = nock(baseUrl) + .post(`/v1/containers/${encodeURIComponent(containerName)}/start`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, { success: true }); + + await sdk.container.start(containerName); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('delete', () => { + it('should send DELETE request to /v1/containers/:name', async () => { + const containerName = 'test-container'; + + const scope = nock(baseUrl) + .delete(`/v1/containers/${encodeURIComponent(containerName)}`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, { success: true }); + + await sdk.container.delete(containerName); + + expect(scope.isDone()).toBe(true); + }); + }); + }); + + describe('SandboxSDK', () => { + let sdk: ReturnType; + + beforeEach(() => { + sdk = createSDK(baseUrl, token); + }); + + describe('exec', () => { + it('should send POST request to /v1/sandbox/:name/exec with correct params', async () => { + const sandboxName = 'test-sandbox'; + const execParams: ExecRequest = { + command: 'ls -la', + cwd: '/app' + }; + + const mockResponse = { + success: true, + data: { + success: true, + stdout: 'file1.txt\nfile2.txt', + stderr: '', + exitCode: 0, + cwd: '/app' + } + }; + + const scope = nock(baseUrl) + .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`, (body) => { + expect(body).toMatchObject(execParams); + return true; + }) + .matchHeader('authorization', `Bearer ${token}`) + .matchHeader('content-type', 'application/json') + .reply(200, mockResponse); + + const result = await sdk.sandbox.exec(sandboxName, execParams); + + expect(scope.isDone()).toBe(true); + expect(result.stdout).toBe('file1.txt\nfile2.txt'); + expect(result.exitCode).toBe(0); + }); + + it('should send POST request with command only (no cwd)', async () => { + const sandboxName = 'test-sandbox'; + const execParams: ExecRequest = { + command: 'pwd' + }; + + const mockResponse = { + success: true, + data: { + success: true, + stdout: '/app', + stderr: '', + exitCode: 0 + } + }; + + const scope = nock(baseUrl) + .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`, (body) => { + expect(body.command).toBe(execParams.command); + expect(body).not.toHaveProperty('cwd'); + return true; + }) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + await sdk.sandbox.exec(sandboxName, execParams); + + expect(scope.isDone()).toBe(true); + }); + + it('should handle special characters in sandbox name', async () => { + const sandboxName = 'test-sandbox@v1.0'; + const execParams: ExecRequest = { + command: 'echo hello' + }; + + const mockResponse = { + success: true, + data: { + success: true, + stdout: 'hello', + stderr: '', + exitCode: 0 + } + }; + + const scope = nock(baseUrl) + .post(`/v1/sandbox/${encodeURIComponent(sandboxName)}/exec`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + await sdk.sandbox.exec(sandboxName, execParams); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('health', () => { + it('should send GET request to /v1/sandbox/:name/health', async () => { + const sandboxName = 'test-sandbox'; + + const mockResponse = { + success: true, + healthy: true + }; + + const scope = nock(baseUrl) + .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + const result = await sdk.sandbox.health(sandboxName); + + expect(scope.isDone()).toBe(true); + expect(result).toBe(true); + }); + + it('should return false when sandbox is unhealthy', async () => { + const sandboxName = 'test-sandbox'; + + const mockResponse = { + success: true, + healthy: false + }; + + const scope = nock(baseUrl) + .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + const result = await sdk.sandbox.health(sandboxName); + + expect(scope.isDone()).toBe(true); + expect(result).toBe(false); + }); + + it('should handle special characters in sandbox name', async () => { + const sandboxName = 'test-sandbox@v1.0'; + + const mockResponse = { + success: true, + healthy: true + }; + + const scope = nock(baseUrl) + .get(`/v1/sandbox/${encodeURIComponent(sandboxName)}/health`) + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, mockResponse); + + await sdk.sandbox.health(sandboxName); + + expect(scope.isDone()).toBe(true); + }); + }); + }); + + describe('Request Headers and Configuration', () => { + it('should include authorization header in all requests', async () => { + const sdk = createSDK(baseUrl, token); + const customToken = 'custom-token-123'; + const sdkWithCustomToken = createSDK(baseUrl, customToken); + + const scope1 = nock(baseUrl) + .post('/v1/containers') + .matchHeader('authorization', `Bearer ${token}`) + .reply(200, { success: true }); + + const scope2 = nock(baseUrl) + .post('/v1/containers') + .matchHeader('authorization', `Bearer ${customToken}`) + .reply(200, { success: true }); + + await sdk.container.create({ name: 'test1' }); + + await sdkWithCustomToken.container.create({ name: 'test2' }); + + expect(scope1.isDone()).toBe(true); + expect(scope2.isDone()).toBe(true); + }); + + it('should include content-type header in POST requests', async () => { + const sdk = createSDK(baseUrl, token); + + const scope = nock(baseUrl) + .post('/v1/containers') + .matchHeader('content-type', /application\/json/) + .reply(200, { success: true }); + + await sdk.container.create({ name: 'test' }); + + expect(scope.isDone()).toBe(true); + }); + + it('should handle baseUrl with trailing slash', async () => { + const urlWithSlash = 'http://localhost:3000/'; + const sdk = createSDK(urlWithSlash, token); + + // Should still make request to correct URL (without double slash) + const scope = nock('http://localhost:3000') + .get('/v1/sandbox/test/health') + .reply(200, { success: true, healthy: true }); + + await sdk.sandbox.health('test'); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('URL Encoding', () => { + let sdk: ReturnType; + + beforeEach(() => { + sdk = createSDK(baseUrl, token); + }); + + it('should properly encode container names with spaces', async () => { + const name = 'my container'; + const encoded = encodeURIComponent(name); + + const scope = nock(baseUrl) + .get(`/v1/containers/${encoded}`) + .reply(200, { + success: true, + data: { + name: name, + image: { imageName: 'node:18' }, + status: { state: 'Running' } + } + }); + + await sdk.container.get(name); + + expect(scope.isDone()).toBe(true); + }); + + it('should properly encode container names with special chars', async () => { + const name = 'container/name@v1.0'; + const encoded = encodeURIComponent(name); + + const scope = nock(baseUrl) + .get(`/v1/containers/${encoded}`) + .reply(200, { + success: true, + data: { + name: name, + image: { imageName: 'node:18' }, + status: { state: 'Running' } + } + }); + + await sdk.container.get(name); + + expect(scope.isDone()).toBe(true); + }); + + it('should properly encode sandbox names with unicode chars', async () => { + const name = '测试-sandbox'; + const encoded = encodeURIComponent(name); + + const scope = nock(baseUrl) + .get(`/v1/sandbox/${encoded}/health`) + .reply(200, { success: true, healthy: true }); + + await sdk.sandbox.health(name); + + expect(scope.isDone()).toBe(true); + }); + }); + + describe('Base URL Configuration', () => { + it('should use correct base URL /v1 prefix', async () => { + const sdk = createSDK(baseUrl, token); + + // Should request to /v1/containers, not /containers + const scope = nock(baseUrl) + .get('/v1/containers/test') + .reply(200, { + success: true, + data: { + name: 'test', + image: { imageName: 'node:18' }, + status: { state: 'Running' } + } + }); + + await sdk.container.get('test'); + + expect(scope.isDone()).toBe(true); + }); + + it('should work with different ports', async () => { + const customBaseUrl = 'http://localhost:8080'; + const sdk = createSDK(customBaseUrl, token); + + const scope = nock(customBaseUrl) + .get('/v1/sandbox/test/health') + .reply(200, { success: true, healthy: true }); + + await sdk.sandbox.health('test'); + + expect(scope.isDone()).toBe(true); + }); + }); +}); diff --git a/projects/sandbox_server/test/middleware/auth.test.ts b/projects/sandbox_server/test/middleware/auth.test.ts new file mode 100644 index 0000000000..0a4691b6f2 --- /dev/null +++ b/projects/sandbox_server/test/middleware/auth.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect } from 'vitest'; +import { Hono } from 'hono'; +import { authMiddleware } from '../../src/middleware/auth'; +import { errorHandler } from '../../src/middleware/error'; + +describe('Auth Middleware', () => { + const createTestApp = () => { + const app = new Hono(); + app.onError(errorHandler); + app.use('*', authMiddleware); + app.get('/protected', (c) => c.json({ success: true })); + return app; + }; + + describe('Authorization header validation', () => { + it('should return 401 when Authorization header is missing', async () => { + const app = createTestApp(); + const res = await app.request('/protected'); + + expect(res.status).toBe(401); + const data = (await res.json()) as { message: string }; + expect(data.message).toBe('Authorization header is required'); + }); + + it('should return 401 when Authorization format is invalid (no Bearer prefix)', async () => { + const app = createTestApp(); + const res = await app.request('/protected', { + headers: { + Authorization: 'test-token' + } + }); + + expect(res.status).toBe(401); + const data = (await res.json()) as { message: string }; + expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); + }); + + it('should return 401 when Authorization format is invalid (wrong prefix)', async () => { + const app = createTestApp(); + const res = await app.request('/protected', { + headers: { + Authorization: 'Basic test-token' + } + }); + + expect(res.status).toBe(401); + const data = (await res.json()) as { message: string }; + expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); + }); + }); + + describe('Token validation', () => { + it('should return 401 when token is invalid', async () => { + const app = createTestApp(); + const res = await app.request('/protected', { + headers: { + Authorization: 'Bearer invalid-token' + } + }); + + expect(res.status).toBe(401); + const data = (await res.json()) as { message: string }; + expect(data.message).toBe('Invalid token'); + }); + + it('should return 401 when token is empty', async () => { + const app = createTestApp(); + const res = await app.request('/protected', { + headers: { + Authorization: 'Bearer ' + } + }); + + expect(res.status).toBe(401); + const data = (await res.json()) as { message: string }; + // Note: 'Bearer ' (with trailing space but no token) is treated as invalid format + expect(data.message).toBe('Invalid authorization format. Expected: Bearer '); + }); + + it('should allow request with valid token', async () => { + const app = createTestApp(); + const res = await app.request('/protected', { + headers: { + Authorization: 'Bearer test-token' // matches env.TOKEN in test mode + } + }); + + expect(res.status).toBe(200); + const data = (await res.json()) as { success: boolean }; + expect(data.success).toBe(true); + }); + }); +}); diff --git a/projects/sandbox_server/test/middleware/error.test.ts b/projects/sandbox_server/test/middleware/error.test.ts new file mode 100644 index 0000000000..bb34256a70 --- /dev/null +++ b/projects/sandbox_server/test/middleware/error.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; +import { z } from 'zod'; +import { errorHandler } from '../../src/middleware/error'; + +describe('Error Handler', () => { + let consoleSpy: ReturnType; + + beforeEach(() => { + consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + }); + + const createTestApp = (errorToThrow: () => Error) => { + const app = new Hono(); + app.onError(errorHandler); + app.get('/error', () => { + throw errorToThrow(); + }); + return app; + }; + + describe('HTTPException handling', () => { + it('should handle HTTPException with 401 status', async () => { + const app = createTestApp(() => new HTTPException(401, { message: 'Unauthorized' })); + const res = await app.request('/error'); + + expect(res.status).toBe(401); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + expect(data.message).toBe('Unauthorized'); + }); + + it('should handle HTTPException with 403 status', async () => { + const app = createTestApp(() => new HTTPException(403, { message: 'Forbidden' })); + const res = await app.request('/error'); + + expect(res.status).toBe(403); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + expect(data.message).toBe('Forbidden'); + }); + + it('should handle HTTPException with 404 status', async () => { + const app = createTestApp(() => new HTTPException(404, { message: 'Not Found' })); + const res = await app.request('/error'); + + expect(res.status).toBe(404); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + expect(data.message).toBe('Not Found'); + }); + + it('should handle HTTPException with 500 status', async () => { + const app = createTestApp(() => new HTTPException(500, { message: 'Internal Server Error' })); + const res = await app.request('/error'); + + expect(res.status).toBe(500); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + expect(data.message).toBe('Internal Server Error'); + }); + }); + + describe('ZodError handling', () => { + it('should handle ZodError with single issue', async () => { + const schema = z.object({ + name: z.string().min(1) + }); + + const app = createTestApp(() => { + const result = schema.safeParse({ name: '' }); + if (!result.success) { + return result.error; + } + return new Error('Unexpected'); + }); + const res = await app.request('/error'); + + expect(res.status).toBe(400); + const data = (await res.json()) as { + success: boolean; + message: string; + errors: Array<{ code: string; path: string[] }>; + }; + expect(data.success).toBe(false); + expect(data.message).toBe('Validation error'); + expect(data.errors).toBeDefined(); + expect(Array.isArray(data.errors)).toBe(true); + expect(data.errors.length).toBeGreaterThan(0); + }); + + it('should handle ZodError with multiple issues', async () => { + const schema = z.object({ + name: z.string().min(1), + age: z.number().positive() + }); + + const app = createTestApp(() => { + const result = schema.safeParse({ name: '', age: -1 }); + if (!result.success) { + return result.error; + } + return new Error('Unexpected'); + }); + const res = await app.request('/error'); + + expect(res.status).toBe(400); + const data = (await res.json()) as { + success: boolean; + message: string; + errors: Array<{ code: string; path: string[] }>; + }; + expect(data.success).toBe(false); + expect(data.message).toBe('Validation error'); + expect(data.errors.length).toBe(2); + }); + }); + + describe('Generic Error handling', () => { + it('should handle generic Error with custom message', async () => { + const app = createTestApp(() => new Error('Something went wrong')); + const res = await app.request('/error'); + + expect(res.status).toBe(500); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + expect(data.message).toBe('Something went wrong'); + }); + + it('should handle Error without message', async () => { + const app = createTestApp(() => new Error()); + const res = await app.request('/error'); + + expect(res.status).toBe(500); + const data = (await res.json()) as { success: boolean; message: string }; + expect(data.success).toBe(false); + // Empty error message results in empty string + expect(data.message).toBe(''); + }); + }); + + describe('Error logging', () => { + it('should log errors to console', async () => { + const error = new Error('Test error'); + const app = createTestApp(() => error); + await app.request('/error'); + + expect(consoleSpy).toHaveBeenCalledWith('[Error]', error); + }); + }); +}); diff --git a/projects/sandbox_server/test/setup.ts b/projects/sandbox_server/test/setup.ts new file mode 100644 index 0000000000..80ce77af58 --- /dev/null +++ b/projects/sandbox_server/test/setup.ts @@ -0,0 +1,16 @@ +import { beforeAll, afterAll, vi } from 'vitest'; +import { loadEnvFiles } from './utils/env'; + +// Set test environment +process.env.NODE_ENV = 'test'; + +// Load environment variables from .env.test.local if exists +loadEnvFiles({ envFileNames: ['.env.test.local'] }); + +beforeAll(() => { + // Additional setup if needed +}); + +afterAll(() => { + vi.restoreAllMocks(); +}); diff --git a/projects/sandbox_server/test/utils/env.ts b/projects/sandbox_server/test/utils/env.ts new file mode 100644 index 0000000000..8e519e9559 --- /dev/null +++ b/projects/sandbox_server/test/utils/env.ts @@ -0,0 +1,38 @@ +import { existsSync, readFileSync } from 'fs'; +import { resolve } from 'path'; + +type LoadVectorEnvOptions = { + envFileNames?: string[]; +}; + +const parseEnvFile = (filePath: string) => { + const content = readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + + for (const rawLine of lines) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) continue; + + const separatorIndex = line.indexOf('='); + if (separatorIndex === -1) continue; + + const key = line.slice(0, separatorIndex).trim(); + const value = line.slice(separatorIndex + 1).trim(); + + if (!key || process.env[key]) continue; + process.env[key] = value; + } +}; + +export const loadEnvFiles = (options: LoadVectorEnvOptions = {}) => { + const envFileNames = options.envFileNames ?? ['.env.test.local']; + // __dirname is test/utils/, go up one level to test/ + const baseDir = resolve(__dirname, '..'); + + for (const envFileName of envFileNames) { + const filePath = resolve(baseDir, envFileName); + if (existsSync(filePath)) { + parseEnvFile(filePath); + } + } +}; diff --git a/projects/sandbox_server/tsconfig.json b/projects/sandbox_server/tsconfig.json new file mode 100644 index 0000000000..4ec1e3446a --- /dev/null +++ b/projects/sandbox_server/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "lib": ["ES2022"], + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules"] +} diff --git a/projects/sandbox_server/vitest.config.ts b/projects/sandbox_server/vitest.config.ts new file mode 100644 index 0000000000..667ff854b4 --- /dev/null +++ b/projects/sandbox_server/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + setupFiles: ['./test/setup.ts'], + include: ['test/**/*.test.ts'], + hookTimeout: 120000, + coverage: { + reporter: ['text', 'json', 'html'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.schema.ts', 'src/sdk/**/*'] + } + }, + resolve: { + alias: { + '@': './src' + } + } +});