Commit 44d64ce4 by Archer Committed by GitHub

Sandbox server (#6383)

* feat: sandbox_server

* docker build

* action
parent 214b3138
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
......@@ -6,6 +6,7 @@ description: 'FastGPT V4.14.7 更新说明'
## 🚀 新增内容
1. 知识库搜索,支持指定 collectionIds 来进行筛选。
## ⚙️ 优化
......
......@@ -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",
......
......@@ -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:
......
packages:
- packages/*
- projects/*
- projects/app
- projects/marketplace
- projects/mcp_server
- projects/sandbox
- scripts/icon
- sdk/*
# 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
# 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
# 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
# ==================== 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"]
# 快速开始指南
## ✅ 项目已完成
所有功能已实现并通过测试。
## 📁 项目结构
```
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`
---
🎉 **项目已完成!所有功能均已实现并通过测试。**
# 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
```
{
"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=="],
}
}
{
"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"
}
}
dist
node_modules
*.log
.DS_Store
# 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。
{
"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=="],
}
}
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<void> {
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<ContainerInfo | null> {
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<void> {
const response = await this.client.post(`/containers/${encodeURIComponent(name)}/pause`);
SuccessResponseSchema.parse(response.data);
}
/**
* Start a paused container
*/
async start(name: string): Promise<void> {
const response = await this.client.post(`/containers/${encodeURIComponent(name)}/start`);
SuccessResponseSchema.parse(response.data);
}
/**
* Delete a container
*/
async delete(name: string): Promise<void> {
const response = await this.client.delete(`/containers/${encodeURIComponent(name)}`);
SuccessResponseSchema.parse(response.data);
}
}
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);
}
{
"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"
}
}
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<ExecResponse> {
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<boolean> {
const response = await this.client.get(`/sandbox/${encodeURIComponent(name)}/health`);
const result = HealthCheckResponseSchema.parse(response.data);
return result.healthy;
}
}
/**
* 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
});
{
"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"]
}
/**
* 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;
}
export { SealosClient, createSealosClient } from './sealos';
export { SandboxClient, createSandboxClient } from './sandbox';
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<HealthResponse> {
const response = await this.client.get('/health');
return HealthResponseSchema.parse(response.data);
}
/**
* Check if sandbox is healthy (boolean)
*/
async isHealthy(): Promise<boolean> {
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<ExecResponse> {
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);
}
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<void> {
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<ContainerInfo | null> {
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<void> {
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<void> {
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<void> {
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();
}
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
};
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 };
import { createMiddleware } from 'hono/factory';
import { HTTPException } from 'hono/http-exception';
import { env } from '../env';
/**
* Bearer token authentication middleware
* Validates Authorization header: Bearer <token>
*/
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 <token>'
});
}
const token = authorization.slice(7);
if (token !== env.TOKEN) {
throw new HTTPException(401, { message: 'Invalid token' });
}
await next();
});
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
);
};
export { authMiddleware } from './auth';
export { errorHandler } from './error';
export { loggerMiddleware, setLoggerError } from './logger';
import { createMiddleware } from 'hono/factory';
import { logger } from '../utils';
// Store for request timing and error info
const requestStore = new WeakMap<Request, { startTime: number; errorMessage?: string }>();
/**
* 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;
}
}
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;
};
export { createContainerRoutes } from './container.route';
export { createSandboxRoutes } from './sandbox.route';
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;
};
import type { z } from '@hono/zod-openapi';
export declare const SuccessResponseSchema: z.ZodObject<
{
success: z.ZodLiteral<true>;
},
z.core.$strip
>;
export type SuccessResponse = z.infer<typeof SuccessResponseSchema>;
export declare const ErrorResponseSchema: z.ZodObject<
{
success: z.ZodLiteral<false>;
message: z.ZodString;
},
z.core.$strip
>;
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
export declare const NameParamSchema: z.ZodObject<
{
name: z.ZodString;
},
z.core.$strip
>;
export type NameParam = z.infer<typeof NameParamSchema>;
import { z } from '@hono/zod-openapi';
// Common response wrapper
export const SuccessResponseSchema = z.object({
success: z.literal(true)
});
export type SuccessResponse = z.infer<typeof SuccessResponseSchema>;
export const ErrorResponseSchema = z.object({
success: z.literal(false),
message: z.string()
});
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
// 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<typeof NameParamSchema>;
import type { z } from '@hono/zod-openapi';
export declare const CreateContainerSchema: z.ZodObject<
{
name: z.ZodString;
},
z.core.$strip
>;
export type CreateContainerInput = z.infer<typeof CreateContainerSchema>;
export declare const ContainerStatusSchema: z.ZodObject<
{
state: z.ZodEnum<{
Running: 'Running';
Creating: 'Creating';
Paused: 'Paused';
Error: 'Error';
Unknown: 'Unknown';
}>;
replicas: z.ZodOptional<z.ZodNumber>;
availableReplicas: z.ZodOptional<z.ZodNumber>;
},
z.core.$strip
>;
export type ContainerStatus = z.infer<typeof ContainerStatusSchema>;
export declare const ContainerServerSchema: z.ZodObject<
{
serviceName: z.ZodString;
number: z.ZodNumber;
publicDomain: z.ZodOptional<z.ZodString>;
domain: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
export type ContainerServer = z.infer<typeof ContainerServerSchema>;
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<z.ZodNumber>;
availableReplicas: z.ZodOptional<z.ZodNumber>;
},
z.core.$strip
>;
server: z.ZodOptional<
z.ZodObject<
{
serviceName: z.ZodString;
number: z.ZodNumber;
publicDomain: z.ZodOptional<z.ZodString>;
domain: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>
>;
createdAt: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
export type ContainerInfo = z.infer<typeof ContainerInfoSchema>;
export declare const ContainerInfoResponseSchema: z.ZodObject<
{
success: z.ZodLiteral<true>;
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<z.ZodNumber>;
availableReplicas: z.ZodOptional<z.ZodNumber>;
},
z.core.$strip
>;
server: z.ZodOptional<
z.ZodObject<
{
serviceName: z.ZodString;
number: z.ZodNumber;
publicDomain: z.ZodOptional<z.ZodString>;
domain: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>
>;
createdAt: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
},
z.core.$strip
>;
export type ContainerInfoResponse = z.infer<typeof ContainerInfoResponseSchema>;
export declare const SealosContainerResponseSchema: z.ZodObject<
{
name: z.ZodString;
image: z.ZodObject<
{
imageName: z.ZodString;
},
z.core.$strip
>;
createTime: z.ZodOptional<z.ZodString>;
status: z.ZodObject<
{
replicas: z.ZodCoercedNumber<unknown>;
availableReplicas: z.ZodCoercedNumber<unknown>;
isPause: z.ZodCoercedBoolean<unknown>;
},
z.core.$strip
>;
ports: z.ZodArray<
z.ZodObject<
{
serviceName: z.ZodString;
number: z.ZodCoercedNumber<unknown>;
publicDomain: z.ZodOptional<z.ZodString>;
domain: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>
>;
},
z.core.$strip
>;
export type SealosContainerResponse = z.infer<typeof SealosContainerResponseSchema>;
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<typeof CreateContainerSchema>;
// ==================== 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<typeof ContainerStatusSchema>;
export const ContainerServerSchema = z.object({
serviceName: z.string(),
number: z.number(),
publicDomain: z.string().optional(),
domain: z.string().optional()
});
export type ContainerServer = z.infer<typeof ContainerServerSchema>;
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<typeof ContainerInfoSchema>;
export const ContainerInfoResponseSchema = z.object({
success: z.literal(true),
data: ContainerInfoSchema
});
export type ContainerInfoResponse = z.infer<typeof ContainerInfoResponseSchema>;
// ==================== 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<typeof SealosContainerResponseSchema>;
export * from './common.schema';
export * from './container.schema';
export * from './sandbox.schema';
export * from './common.schema';
export * from './container.schema';
export * from './sandbox.schema';
import type { z } from '@hono/zod-openapi';
export declare const ExecRequestSchema: z.ZodObject<
{
command: z.ZodString;
cwd: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
export type ExecRequest = z.infer<typeof ExecRequestSchema>;
export declare const HealthResponseSchema: z.ZodObject<
{
status: z.ZodString;
timestamp: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
export type HealthResponse = z.infer<typeof HealthResponseSchema>;
export declare const ExecResponseSchema: z.ZodObject<
{
success: z.ZodBoolean;
stdout: z.ZodString;
stderr: z.ZodString;
exitCode: z.ZodNumber;
cwd: z.ZodOptional<z.ZodString>;
error: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
export type ExecResponse = z.infer<typeof ExecResponseSchema>;
export declare const HealthCheckResponseSchema: z.ZodObject<
{
success: z.ZodLiteral<true>;
healthy: z.ZodBoolean;
},
z.core.$strip
>;
export type HealthCheckResponse = z.infer<typeof HealthCheckResponseSchema>;
export declare const ExecResultResponseSchema: z.ZodObject<
{
success: z.ZodLiteral<true>;
data: z.ZodObject<
{
success: z.ZodBoolean;
stdout: z.ZodString;
stderr: z.ZodString;
exitCode: z.ZodNumber;
cwd: z.ZodOptional<z.ZodString>;
error: z.ZodOptional<z.ZodString>;
},
z.core.$strip
>;
},
z.core.$strip
>;
export type ExecResultResponse = z.infer<typeof ExecResultResponseSchema>;
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<typeof ExecRequestSchema>;
// ==================== Response Schemas ====================
export const HealthResponseSchema = z.object({
status: z.string(),
timestamp: z.string().optional()
});
export type HealthResponse = z.infer<typeof HealthResponseSchema>;
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<typeof ExecResponseSchema>;
export const HealthCheckResponseSchema = z.object({
success: z.literal(true),
healthy: z.boolean()
});
export type HealthCheckResponse = z.infer<typeof HealthCheckResponseSchema>;
export const ExecResultResponseSchema = z.object({
success: z.literal(true),
data: ExecResponseSchema
});
export type ExecResultResponse = z.infer<typeof ExecResultResponseSchema>;
export { logger } from './logger';
/**
* 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<LogLevel, string> = {
DEBUG: colors.gray,
INFO: colors.green,
WARN: colors.yellow,
ERROR: colors.red
};
const methodColors: Record<string, string> = {
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<string, unknown>
): 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<string, unknown>) =>
log('DEBUG', category, message, meta),
info: (category: string, message: string, meta?: Record<string, unknown>) =>
log('INFO', category, message, meta),
warn: (category: string, message: string, meta?: Record<string, unknown>) =>
log('WARN', category, message, meta),
error: (category: string, message: string, meta?: Record<string, unknown>) =>
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;
# 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
# Ignore local test environment files
.env.test.local
# 测试说明
## 测试类型
### 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
```
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);
});
});
});
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<void> {
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<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
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<void> {
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<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
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<typeof createSDK>;
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<typeof createSDK>;
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<typeof createSDK>;
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);
});
});
});
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 <token>');
});
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 <token>');
});
});
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 <token>');
});
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);
});
});
});
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<typeof vi.spyOn>;
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);
});
});
});
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();
});
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);
}
}
};
{
"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"]
}
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'
}
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment