diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..faa929c --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,109 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + verify: + runs-on: easyai-gateway-ci-unprivileged-v2 + env: + TRIVY_DB_REPOSITORY: ghcr.m.daocloud.io/aquasecurity/trivy-db:2 + steps: + - name: Checkout without external Actions + env: + CI_REPOSITORY: ${{ github.repository }} + CI_SERVER_URL: ${{ github.server_url }} + CI_SHA: ${{ github.sha }} + CI_JOB_TOKEN: ${{ github.token }} + CI_EVENT_BEFORE: ${{ github.event.before }} + CI_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + set -eu + test -n "$CI_JOB_TOKEN" + authorization=$(printf 'x-access-token:%s' "$CI_JOB_TOKEN" | base64 | tr -d '\n') + git init . + git -c "http.extraHeader=AUTHORIZATION: basic $authorization" \ + fetch --no-tags "$CI_SERVER_URL/$CI_REPOSITORY.git" "$CI_SHA" + for comparison_sha in "$CI_EVENT_BEFORE" "$CI_PR_BASE_SHA"; do + case "$comparison_sha" in + [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) + if test "${#comparison_sha}" -eq 40 && \ + test "$comparison_sha" != 0000000000000000000000000000000000000000; then + git -c "http.extraHeader=AUTHORIZATION: basic $authorization" \ + fetch --no-tags "$CI_SERVER_URL/$CI_REPOSITORY.git" "$comparison_sha" + fi + ;; + esac + done + unset authorization CI_JOB_TOKEN + test ! -f .git/shallow + git checkout --detach "$CI_SHA" + test "$(git rev-parse HEAD)" = "$CI_SHA" + - name: Verify pinned host toolchains + run: | + go version + node --version + pnpm --version + docker-compose version + shellcheck --version + trivy --version + govulncheck -version + - name: Verify production migration safety + env: + CI_EVENT_NAME: ${{ github.event_name }} + CI_EVENT_BEFORE: ${{ github.event.before }} + CI_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + production_base=$(cat deploy/ci/production-migration-base) + immutable_base=$CI_EVENT_BEFORE + if test "$CI_EVENT_NAME" = pull_request; then + immutable_base=$CI_PR_BASE_SHA + fi + case "$immutable_base" in + [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) + test "${#immutable_base}" -eq 40 + test "$immutable_base" != 0000000000000000000000000000000000000000 + ;; + *) exit 1 ;; + esac + git merge-base --is-ancestor "$immutable_base" HEAD + node ./scripts/ci-validate-migrations.mjs \ + "$production_base" "$immutable_base" + - name: Verify Go formatting + run: | + unformatted=$(gofmt -l apps/api) + test -z "$unformatted" || { + printf 'Go files require gofmt:\n%s\n' "$unformatted" >&2 + exit 1 + } + - name: Verify Go code + working-directory: apps/api + run: | + go vet ./... + go test ./... + govulncheck ./... + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm test + - run: pnpm build + - name: Audit JavaScript dependencies + run: pnpm audit --audit-level high + - name: Validate deployment configuration + run: | + docker-compose -f docker-compose.yml config --quiet + shellcheck scripts/ci-build-images.sh scripts/ci-validate-semver.sh \ + scripts/provision-ci-runner.sh tests/ci/ci-build-images-test.sh \ + tests/ci/migrations-test.sh tests/ci/pipeline-test.sh \ + tests/ci/semver-test.sh + ./tests/ci/ci-build-images-test.sh + ./tests/ci/migrations-test.sh + ./tests/ci/pipeline-test.sh + ./tests/ci/semver-test.sh + - name: Scan repository + run: | + trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL \ + --ignore-unfixed --exit-code 1 --timeout 15m --skip-dirs .git \ + --skip-dirs node_modules . diff --git a/.gitea/workflows/release-ci.yml b/.gitea/workflows/release-ci.yml new file mode 100644 index 0000000..c73b2c9 --- /dev/null +++ b/.gitea/workflows/release-ci.yml @@ -0,0 +1,94 @@ +name: release-ci + +on: + push: + tags: ['v*'] + +jobs: + verify-tag: + runs-on: easyai-gateway-ci-unprivileged-v2 + env: + TRIVY_DB_REPOSITORY: ghcr.m.daocloud.io/aquasecurity/trivy-db:2 + steps: + - name: Checkout without external Actions + env: + CI_REPOSITORY: ${{ github.repository }} + CI_SERVER_URL: ${{ github.server_url }} + CI_SHA: ${{ github.sha }} + CI_JOB_TOKEN: ${{ github.token }} + run: | + set -eu + test -n "$CI_JOB_TOKEN" + authorization=$(printf 'x-access-token:%s' "$CI_JOB_TOKEN" | base64 | tr -d '\n') + git init . + git -c "http.extraHeader=AUTHORIZATION: basic $authorization" \ + fetch --no-tags "$CI_SERVER_URL/$CI_REPOSITORY.git" "$CI_SHA" + unset authorization CI_JOB_TOKEN + test ! -f .git/shallow + git checkout --detach FETCH_HEAD + - name: Verify pinned host toolchains + run: | + go version + node --version + pnpm --version + docker-compose version + shellcheck --version + trivy --version + govulncheck -version + - name: Verify release tag ancestry + env: + CI_REPOSITORY: ${{ github.repository }} + CI_SERVER_URL: ${{ github.server_url }} + CI_SHA: ${{ github.sha }} + CI_JOB_TOKEN: ${{ github.token }} + run: | + set -eu + tag_name=${GITHUB_REF#refs/tags/} + ./scripts/ci-validate-semver.sh "$tag_name" + authorization=$(printf 'x-access-token:%s' "$CI_JOB_TOKEN" | base64 | tr -d '\n') + git -c "http.extraHeader=AUTHORIZATION: basic $authorization" \ + fetch --no-tags "$CI_SERVER_URL/$CI_REPOSITORY.git" \ + +refs/heads/main:refs/remotes/origin/main + unset authorization CI_JOB_TOKEN + test ! -f .git/shallow + test "$(git rev-parse HEAD)" = "$CI_SHA" + git merge-base --is-ancestor "$CI_SHA" refs/remotes/origin/main + - name: Verify production migration safety + run: | + production_base=$(cat deploy/ci/production-migration-base) + node ./scripts/ci-validate-migrations.mjs "$production_base" + - name: Verify Go formatting + run: | + unformatted=$(gofmt -l apps/api) + test -z "$unformatted" || { + printf 'Go files require gofmt:\n%s\n' "$unformatted" >&2 + exit 1 + } + - name: Verify Go code + working-directory: apps/api + run: | + go vet ./... + go test ./... + govulncheck ./... + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm test + - run: pnpm build + - name: Audit JavaScript dependencies + run: pnpm audit --audit-level high + - name: Validate deployment configuration + run: | + docker-compose -f docker-compose.yml config --quiet + shellcheck scripts/ci-build-images.sh scripts/ci-validate-semver.sh \ + scripts/provision-ci-runner.sh tests/ci/ci-build-images-test.sh \ + tests/ci/migrations-test.sh tests/ci/pipeline-test.sh \ + tests/ci/semver-test.sh + ./tests/ci/ci-build-images-test.sh + ./tests/ci/migrations-test.sh + ./tests/ci/pipeline-test.sh + ./tests/ci/semver-test.sh + - name: Scan repository + run: | + trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL \ + --ignore-unfixed --exit-code 1 --timeout 15m --skip-dirs .git \ + --skip-dirs node_modules . diff --git a/README.md b/README.md index 5f98881..aa8464d 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,10 @@ Web 容器的 Nginx 配置通过 bind mount 挂载自仓库文件 [docker/nginx. docker compose -f docker-compose.yml restart web ``` +## 生产 CI/CD + +Gitea Actions 会在隔离的 rootless DinD Runner 中对 Pull Request、`main` Push 和版本 Tag 执行完整质量门禁;Tag 使用独立的 `release-ci / verify-tag (push)` context,不能复用旧的 `main` 成功状态。源码 Job 没有宿主 Docker、`sudo` 或生产部署权限。部署仓的 root-owned dispatcher 只在相同 SHA 的 `main` 与 Tag context 都成功后,用固定命令构建并扫描镜像,再以 Registry digest 发布 `ai.51easyai.com`。安装 Runner、Fork PR 审批、发布验证和回滚步骤见 [生产 CI/CD 运行手册](docs/runbooks/production-ci-cd.md),信任边界见 [ADR-001](docs/decisions/001-production-cicd.md)。 + Compose 默认使用独立容器数据库 `postgres:18-alpine`,数据卷会保留在 `postgres_data` 和 `api_data`。为避免本地开发 `.env` 中的 `localhost` 数据库地址污染容器部署,compose 使用 `AI_GATEWAY_COMPOSE_*` 变量作为容器部署专用覆盖,例如: ```bash diff --git a/apps/api/cmd/migrate/main.go b/apps/api/cmd/migrate/main.go index 1a4d4b9..fe5a9f8 100644 --- a/apps/api/cmd/migrate/main.go +++ b/apps/api/cmd/migrate/main.go @@ -24,6 +24,10 @@ func main() { os.Exit(1) } defer conn.Close(ctx) + if _, err := conn.Exec(ctx, "SET standard_conforming_strings = on"); err != nil { + logger.Error("enforce standard SQL string semantics failed", "error", err) + os.Exit(1) + } if _, err := conn.Exec(ctx, ` CREATE TABLE IF NOT EXISTS schema_migrations ( @@ -64,6 +68,14 @@ CREATE TABLE IF NOT EXISTS schema_migrations ( logger.Error("begin migration failed", "version", version, "error", err) os.Exit(1) } + // Pin string parsing semantics inside every migration transaction. A + // previous migration may have changed the session GUC, while each file is + // parsed and executed independently. + if _, err := tx.Exec(ctx, "SET LOCAL standard_conforming_strings = on"); err != nil { + _ = tx.Rollback(ctx) + logger.Error("enforce migration SQL string semantics failed", "version", version, "error", err) + os.Exit(1) + } if _, err := tx.Exec(ctx, string(sqlBytes)); err != nil { _ = tx.Rollback(ctx) logger.Error("execute migration failed", "version", version, "error", err) diff --git a/apps/api/go.mod b/apps/api/go.mod index 69c6e99..0bfd4ff 100644 --- a/apps/api/go.mod +++ b/apps/api/go.mod @@ -13,7 +13,7 @@ require ( github.com/riverqueue/river v0.24.0 github.com/riverqueue/river/riverdriver/riverpgxv5 v0.24.0 github.com/riverqueue/river/rivertype v0.24.0 - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.52.0 golang.org/x/oauth2 v0.36.0 ) @@ -36,6 +36,6 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/text v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/apps/api/go.sum b/apps/api/go.sum index 7db61cb..1fba1de 100644 --- a/apps/api/go.sum +++ b/apps/api/go.sum @@ -67,14 +67,14 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/apps/web/package.json b/apps/web/package.json index 6b0937a..6b7143f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -44,6 +44,6 @@ "@types/react-dom": "^19.0.0", "tailwindcss": "^4.3.0", "typescript": "^5.8.0", - "vitest": "3.2.4" + "vitest": "3.2.6" } } diff --git a/deploy/ci/act-runner-v2-config.yaml b/deploy/ci/act-runner-v2-config.yaml new file mode 100644 index 0000000..eb33e05 --- /dev/null +++ b/deploy/ci/act-runner-v2-config.yaml @@ -0,0 +1,36 @@ +log: + level: info + +runner: + file: /data/.runner + capacity: 1 + envs: + GOPROXY: "https://goproxy.cn,direct" + GOSUMDB: "sum.golang.google.cn" + PATH: "/opt/easyai-gateway-ci/bin:/opt/easyai-gateway-ci/toolchains/go/bin:/opt/easyai-gateway-ci/toolchains/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + timeout: 3h + shutdown_timeout: 5m + insecure: false + fetch_timeout: 5s + fetch_interval: 2s + labels: + - "easyai-gateway-ci-unprivileged-v2:docker://docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7" + +cache: + enabled: false + +container: + docker_host: "-" + privileged: false + force_pull: false + force_rebuild: false + require_docker: true + docker_timeout: 1m + bind_workdir: false + workdir_parent: workspace + options: "--volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro" + valid_volumes: + - "/opt/easyai-gateway-ci" + +host: + workdir_parent: /data/host-work diff --git a/deploy/ci/easyai-gateway-ci-v2-runner.service b/deploy/ci/easyai-gateway-ci-v2-runner.service new file mode 100644 index 0000000..3f4c9dc --- /dev/null +++ b/deploy/ci/easyai-gateway-ci-v2-runner.service @@ -0,0 +1,44 @@ +[Unit] +Description=EasyAI AI Gateway rootless DinD Gitea Actions runner v2 +Requires=docker.service +After=network-online.target docker.service +Wants=network-online.target + +[Service] +Type=simple +User=root +Group=root +Environment=RUNNER_SECURITY_OPTIONS= +Environment=RUNNER_IMAGE_REF= +EnvironmentFile=-/etc/easyai-gateway-ci-v2/runner.env +ExecStartPre=-/usr/bin/docker rm --force easyai-gateway-ci-v2-runner +ExecStart=/usr/bin/docker run --rm --pull=never --name easyai-gateway-ci-v2-runner --privileged --cpus 2 --memory 2g --memory-swap 3g --pids-limit 1024 $RUNNER_SECURITY_OPTIONS --volume easyai-gateway-ci-v2-data:/data --volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro --volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro --volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro --env CONFIG_FILE=/config.yaml --env DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns --env DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520 --env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns $RUNNER_IMAGE_REF +ExecStop=-/usr/bin/docker stop --time 360 easyai-gateway-ci-v2-runner +Restart=always +RestartSec=5s +TimeoutStartSec=60s +TimeoutStopSec=390s + +# systemd only launches the host Docker client. Pull-request jobs run inside the +# nested rootless daemon and never receive the host or nested Docker socket. +NoNewPrivileges=true +CapabilityBoundingSet= +AmbientCapabilities= +PrivateTmp=true +PrivateDevices=true +ProtectSystem=strict +ProtectHome=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +LockPersonality=true +RestrictRealtime=true +RestrictSUIDSGID=true +SystemCallArchitectures=native +UMask=0077 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/ci/production-migration-base b/deploy/ci/production-migration-base new file mode 100644 index 0000000..31e1d52 --- /dev/null +++ b/deploy/ci/production-migration-base @@ -0,0 +1 @@ +2d6c16fec0bec9c0288e5cb142b458af982fff8f diff --git a/docs/decisions/001-production-cicd.md b/docs/decisions/001-production-cicd.md new file mode 100644 index 0000000..d41a7d7 --- /dev/null +++ b/docs/decisions/001-production-cicd.md @@ -0,0 +1,70 @@ +# ADR-001: 隔离质量 CI 与 root-owned 生产发布 + +## 状态 + +Accepted + +## 日期 + +2026-07-17 + +## 背景 + +AI Gateway 已通过独立部署仓库和 Docker Compose 运行在 `110.42.51.33`,公共入口是 `https://ai.51easyai.com`。同一服务器还运行认证中心、K3s 和其他 EasyAI 服务,磁盘与信任边界都很紧张。Gitea 1.22 对依赖 Job、`environment`、`permissions` 和 `concurrency` 的支持也不能作为生产发布的安全边界。 + +源码仓需要为 Pull Request、`main` 和版本 Tag 提供真实质量门禁;生产发布还必须满足源码可追溯、镜像不可变、数据库迁移前有备份、失败可恢复上一应用版本,以及 Tag 控制的脚本不能直接取得宿主 root 权限。 + +## 决策 + +### 质量 CI + +- `BCAI/easyai-ai-gateway` 使用仓库级 Runner 标签 `easyai-gateway-ci-unprivileged-v2`。PR 与 `main` 由 `ci.yml` 执行,context 分别为 `ci / verify (pull_request)`、`ci / verify (push)`;`v*` Tag 必须由独立 `release-ci.yml` 执行,context 为 `release-ci / verify-tag (push)`。两份 workflow 执行相同质量门禁,但 Tag 不复用 `main` 的旧 context。 +- Runner 外层固定为 `gitea/runner:2.0.0-dind-rootless` 的指定 digest;Job 固定为 `node:24.16.0-bookworm` 的指定 digest。外层按官方 rootless DinD 要求使用 `--privileged`,但内层 Docker daemon 和 Runner 以 UID 1000 运行。 +- 不把宿主或内层 Docker socket 挂入 Job。Runner 配置使用 `docker_host: "-"`、`privileged: false`,工作流唯一允许的 bind source 是只读 `/opt/easyai-gateway-ci`,其中提供固定 Go、Node、pnpm、ShellCheck、Compose、Trivy 和 govulncheck 工具链。ShellCheck 与 Compose 使用官方静态发行物和固定 SHA-256,不复制宿主的可变插件或在 Job 中临时安装。RootlessKit 强制 `--pidns`,即使 PR 传入 `--pid=host`,Job 也不能进入外层 Runner 的 PID namespace;provision 会实测 namespace inode。 +- CI 只执行格式、静态检查、单元测试、依赖审计、Compose 校验、仓库扫描和 CI 自测;不构建发布镜像,不调用 `sudo`,不执行生产发布助手。 +- PR、`main` 与 Tag CI 都把 `deploy/ci/production-migration-base` 中的当前生产提交作为数据库兼容基线:已经存在于生产基线的迁移不得修改或删除,新迁移静态拒绝 DROP、TRUNCATE、DELETE、MERGE、CREATE OR REPLACE、RENAME、列类型/非空收紧等 contract 操作。PR/`main` 还使用事件中不可变的 base/before SHA 比对,确保基线更新稍有滞后时,已经进入 `main` 的新迁移仍不可被改写。 +- 版本 Tag 必须是完整 SemVer,且目标提交必须属于 `main` 历史。该检查是发布前置证据,但本身不授予生产权限。 +- Runner 注册状态保存在专用 Docker named volume,首次注册 Token 只传给短命注册容器;长期 systemd 服务不保存 Token,也不加入宿主 `docker`/`sudo` 用户组。 + +### 生产 CD + +- 生产发布由部署仓的 root-owned dispatcher 负责,和源码 CI Runner 分离。dispatcher 只接受受保护的 `main` 历史版本 Tag,并在对应源码 SHA 上同时等待 `ci / verify (push)` 与本次 Tag 新产生的 `release-ci / verify-tag (push)` 成功。 +- root 服务不执行 Tag 控制的 `pnpm`、测试脚本、源码仓 shell 脚本或应用程序。它只调用部署仓中由 root 固定和审核的命令,通过 BuildKit 构建目标镜像、用 Trivy 扫描镜像、发布 Registry digest,再进行备份、迁移、健康检查和回滚。 +- API/Web 镜像以完整 Git SHA 作为可追溯 Tag,但生产 Compose 使用 Registry digest。服务器为每个 Git SHA 保存 root-only digest 清单,并拒绝把未知的既有 SHA Tag 当作可信发布物。 +- 发布助手在迁移前生成并验证 PostgreSQL custom-format 备份,保留最近 5 份。应用健康检查失败时恢复上一组 digest-pinned 镜像;数据库恢复仍需人工确认。 + +### 残余风险与触发策略 + +rootless DinD 显著缩小了 Job 到宿主 Docker/root 的直接通路,但不是虚拟机级隔离。外层容器仍需 `--privileged`;Runner、rootless Docker、内核或容器运行时漏洞,以及 Gitea 对 `jobs..container.options` 的透传,都可能造成容器边界突破。Job 还保留依赖安装所需的出站网络,并可能消耗共享主机的 CPU、内存和磁盘。因此: + +- 来自同仓受信任分支的 PR 可自动运行;来自 Fork 的 PR 必须先由维护者检查工作流变更并在 Gitea 中批准,不能自动调度。 +- CI Job 不接收生产 Secret、宿主 Docker socket、部署目录或 Runner 状态目录。 +- Runner 容量固定为 1,外层容器设 4096 PID 上限;磁盘在安装前后硬门禁,但共享主机仍不具备完整的 CPU、内存和长期磁盘配额隔离。 +- 中长期应把不受信任 PR CI 迁移到独立主机或虚拟机;当前 rootless Runner 不能被描述为强多租户沙箱。 +- Ubuntu 22.04 没有受限 user namespace,生产主机缺少 `rootlesskit` AppArmor profile 时不硬编码该 profile。provision 会先探测;在启用了 `kernel.apparmor_restrict_unprivileged_userns=1` 的新系统上,如果 profile 缺失则失败,不会降级为 `apparmor=unconfined` 或关闭内核限制。 + +## 备选方案 + +### 让源码 Runner 直接挂载宿主 Docker socket + +拒绝。Docker socket 等价于宿主 root,任何 PR 工作流都可能取得生产服务器控制权,Fork 审批也不能把它变成可靠的技术隔离。 + +### 使用 host executor 但移除 Docker 组 + +拒绝。host Job 仍可读取宿主可见文件、消耗宿主资源并攻击同一用户进程;Gitea 对容器选项的处理也无法为 host executor 提供隔离。 + +### 让 Tag 工作流通过 `sudo` 调用发布脚本 + +拒绝。Tag 可以控制 checkout 中的脚本和应用代码;root 服务不得执行这些内容。固定部署仓 dispatcher 是独立的受保护信任根。 + +### `main` 通过后立即发布或只依赖 `latest` + +拒绝。语义版本 Tag 是明确的发布授权点;digest 才能证明运行中的容器对应不可变产物并可靠选择回滚版本。 + +## 影响 + +- PR、`main` 和版本 Tag 都有自动质量证据,但源码仓 CI 成功不会单独获得生产权限。 +- 创建受保护版本 Tag 后,部署仓 dispatcher 必须同时取得同 SHA 的 `main` context 和独立 Tag context,不能把旧 `main` 成功状态误当作本次 Tag 验证。 +- 首次安装会清理本项目专属 Buildx 缓存并执行 8 GiB 前置、4 GiB 后置磁盘门禁,不会全局 prune 共享 Docker 状态。 +- 首次启用 CD 仍需捕获当前运行镜像作为 bootstrap 回滚基线,并保证数据库迁移对上一应用版本向后兼容。 +- 每次生产发布和恢复演练成功后,必须用单独受审 PR 把 `deploy/ci/production-migration-base` 前移到刚刚部署的完整源码 SHA;不得在发布成功前前移。静态门禁不能证明任意数据变换可逆,生产发布仍必须先做可恢复备份并定期执行真实 `pg_restore` 演练。 diff --git a/docs/runbooks/production-ci-cd.md b/docs/runbooks/production-ci-cd.md new file mode 100644 index 0000000..f3e7a9c --- /dev/null +++ b/docs/runbooks/production-ci-cd.md @@ -0,0 +1,157 @@ +# 生产 CI/CD 运行手册 + +## 信任边界与固定资源 + +- 源码仓:`https://git.51easyai.com/BCAI/easyai-ai-gateway` +- CI Runner:`easyai-gateway-ci-v2-runner.service` +- CI 标签:`easyai-gateway-ci-unprivileged-v2` +- Runner 状态:Docker named volume `easyai-gateway-ci-v2-data` 内的 `/data/.runner` +- 只读工具链:`/opt/easyai-gateway-ci` +- Runner 配置:`/etc/easyai-gateway-ci-v2/config.yaml` +- Runner 运行参数:`/etc/easyai-gateway-ci-v2/runner.env` +- 生产入口:`https://ai.51easyai.com` +- 部署目录:`/root/easyai-ai-gateway-deploy` + +源码仓只执行质量 CI:PR context 是 `ci / verify (pull_request)`,`main` context 是 `ci / verify (push)`,版本 Tag 使用独立 workflow/context `release-ci / verify-tag (push)`。三者都执行生产迁移兼容门禁、Go 格式/vet/test/govulncheck、前端 lint/test/build、依赖审计、Compose 校验、Trivy 仓库扫描和 CI 脚本自测。Job 没有 Docker socket、`sudo` 或部署目录权限,也不构建发布镜像。 + +生产 CD 由部署仓的 root-owned dispatcher 执行。它在相同源码 SHA 上同时等待 `ci / verify (push)` 与本次新产生的 `release-ci / verify-tag (push)` 成功,然后只运行部署仓中固定的 BuildKit 构建、Trivy 镜像扫描、digest 发布、备份、迁移、健康检查和回滚命令;不得执行 Tag checkout 中的 `pnpm`、测试、shell 脚本或应用程序。 + +provision 安装 checksum-pinned 的官方静态 ShellCheck `0.11.0` 和 Docker Compose `5.3.1`,不会依赖宿主是否预装这些命令。所有下载都在使用前校验固定 SHA-256,并在精确 Node Job 镜像内实际执行版本检查。 + +## 首次安装或修复 CI Runner + +前置条件:Linux x86_64、可用 Docker Engine、root 权限,以及 Docker 文件系统至少 8 GiB 可用空间。脚本只压缩本项目专属 `easyai-gateway-ci` Buildx 缓存,不运行全局 `docker system prune`,因为该服务器与其他服务共享 Docker 状态。 + +从 Gitea 仓库设置的 Actions Runner 页面取得一次性仓库注册 Token,在服务器可信的源码 checkout 中执行: + +```bash +RUNNER_REGISTRATION_TOKEN='<一次性 Token>' ./scripts/provision-ci-runner.sh +``` + +已有有效 named volume 时可直接重跑,不再需要 Token: + +```bash +./scripts/provision-ci-runner.sh +``` + +注册 Token 通过交互 stdin 送给短命注册容器,不出现在宿主或容器的命令行参数中;provision 随后立即从环境中清除它,长期 systemd unit 也不包含 Token。不要输出或复制 `/data/.runner` 内容。脚本会停用旧 host Runner,并确保旧 Runner 用户和 v2 遗留用户不属于 `docker`/`sudo` 组。 + +默认磁盘门禁可用环境变量提高,但生产环境不应降低: + +```bash +CI_RUNNER_MIN_FREE_GIB=8 \ +CI_RUNNER_MIN_POST_INSTALL_FREE_GIB=4 \ +RUNNER_REGISTRATION_TOKEN='<一次性 Token>' \ +./scripts/provision-ci-runner.sh +``` + +## AppArmor 分支 + +Ubuntu 22.04 默认没有 `kernel.apparmor_restrict_unprivileged_userns` 限制,当前服务器没有 `rootlesskit` profile 是受支持路径。provision 会实测 Docker 是否接受该 profile;不存在时写入: + +```text +RUNNER_SECURITY_OPTIONS="" +``` + +如果新系统的 `/proc/sys/kernel/apparmor_restrict_unprivileged_userns` 为 `1`,则必须先由宿主发行版或 Docker 安装包提供并加载 `rootlesskit` profile;provision 只探测和使用,不会自行复制一份 profile。缺失时 provision 会停止;不得手工关闭该 sysctl,也不得使用 `apparmor=unconfined` 绕过。 + +参考:[Docker Rootless Docker-in-Docker](https://docs.docker.com/engine/security/rootless/tips/#rootless-docker-in-docker)、[Ubuntu 安全特性版本矩阵](https://documentation.ubuntu.com/security/security-features/security-features-overview/)。 + +## Runner 真实验证 + +安装脚本会先启动一个无 Token、无持久 Runner 状态挂载、无宿主 bind mount 的短命 rootless DinD probe;随后启动长期服务,确认 Runner 已向 Gitea declare、通过内层 `docker info` 验证 `name=rootless`,再从内层 daemon 拉取固定 digest 的 Job 镜像,并在该镜像中实际运行全部工具。人工复核: + +```bash +systemctl is-active easyai-gateway-ci-v2-runner.service +systemctl is-enabled easyai-gateway-ci-v2-runner.service +systemctl cat easyai-gateway-ci-v2-runner.service +journalctl -u easyai-gateway-ci-v2-runner.service --since '15 minutes ago' --no-pager + +docker inspect -f 'user={{.Config.User}} privileged={{.HostConfig.Privileged}} apparmor={{.AppArmorProfile}}' \ + easyai-gateway-ci-v2-runner +docker inspect -f '{{range .Mounts}}{{println .Source " -> " .Destination .Mode}}{{end}}' \ + easyai-gateway-ci-v2-runner +docker exec easyai-gateway-ci-v2-runner docker info \ + --format '{{range .SecurityOptions}}{{println .}}{{end}}' +docker exec easyai-gateway-ci-v2-runner docker image inspect \ + 'docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7' \ + --format '{{.Id}}' +``` + +预期:外层 image user 为 `rootless`、外层因 DinD 为 `privileged=true`、内层安全选项含 `name=rootless`;RootlessKit 使用独立 PID namespace,内层 `--pid=host` 也不能看到外层 Runner;挂载仅包含专用 `/data`、只读配置和只读工具链,不能出现 `/var/run/docker.sock` 或 `/run/docker.sock`。 + +在 Gitea 中分别验证: + +1. 同仓 PR 的 `ci / verify (pull_request)` 成功。 +2. `main` Push 的 `ci / verify (push)` 成功。 +3. 合法 SemVer Tag 新产生独立 `release-ci / verify-tag (push)`;非法或不属于 `main` 历史的 Tag 失败,且 Tag context 不能被旧 `main` context 替代。 +4. Fork PR 默认不自动调度,维护者先检查 `.gitea/workflows/**` 和容器选项后再批准。 + +PR 与 `main` Push workflow 都校验事件携带的 base/before SHA 必须是触发 SHA 的祖先;分支落后或主干历史被改写时 CI 会 fail-closed。PR 先合并最新 `main` 再重跑,Gitea 分支保护同时启用 `block_on_outdated_branch`。 + +## Fork PR 残余风险 + +rootless DinD 不是 VM 沙箱。外层仍按官方要求使用 `--privileged`,Gitea runner 也会解析 PR 控制的 `jobs..container.options`。配置已禁止 Job privileged、宿主/内层 Docker socket 和任意 bind source,但 Runner、rootless Docker、容器运行时或内核漏洞仍可能突破边界;Job 的出站网络和资源消耗也不是强租户隔离。因此 Fork PR 必须维护者批准,CI 不得注入生产 Secret;未来应迁移到独立 CI 主机或虚拟机。 + +## 数据库迁移兼容门禁 + +`deploy/ci/production-migration-base` 必须等于当前生产 API 对应的完整源码 SHA。`scripts/ci-validate-migrations.mjs` 只允许新增、按文件名严格追加且为普通文件的迁移;生产已知迁移以及已经进入 `main` 的迁移不可修改或删除。DROP、TRUNCATE、DELETE、MERGE、CREATE OR REPLACE、动态 EXECUTE、事务控制、DO/函数/过程定义、RENAME、列类型变更、增加/设置 `NOT NULL`、删除默认值和 REVOKE 会直接使 CI 失败。需要过程式迁移时先拆成可审计的声明式 expand/contract SQL;不能把过程体藏在普通或 dollar-quoted 字符串中。 + +该规则允许 INSERT/UPDATE 等数据迁移,因此不能替代备份和恢复验证。每次生产发布成功并完成 `pg_restore` 演练后,创建一个只前移 `deploy/ci/production-migration-base` 的受审 PR;不要把基线指向尚未成功部署的提交。 + +## 发布生产版本 + +1. 合并到受保护 `main`,确认源码 CI 成功。 +2. 在已验证提交上创建完整 SemVer Tag 并推送。 +3. 确认同一 SHA 的 `ci / verify (push)` 和本次 `release-ci / verify-tag (push)` 都成功。 +4. 由部署仓 root-owned dispatcher 构建、扫描并发布 digest;源码仓工作流不会直接调用生产 helper。 +5. 发布、健康检查和恢复演练完成后,用单独 PR 把 `deploy/ci/production-migration-base` 更新为该生产 Tag 的完整源码 SHA。 + +```bash +git switch main +git pull --ff-only +git tag -a v0.1.1 -m 'release: v0.1.1' +git push origin v0.1.1 +``` + +dispatcher 以完整 Git SHA 发布 Registry Tag,并把 Registry 返回的 digest 写入服务器 root-only 发布清单。生产 Compose 使用 `repository@sha256:...`,不是 `latest`、版本号或可变 SHA Tag。 + +## 发布后验证 + +```bash +curl -fsS https://ai.51easyai.com/gateway-api/healthz +curl -fsS https://ai.51easyai.com/gateway-api/readyz +ssh root@110.42.51.33 'cd /root/easyai-ai-gateway-deploy && ./gateway-ops.sh ps' +``` + +同时检查服务器磁盘、API 日志和数据库容器健康状态。发布后的首小时如出现错误率翻倍、P95 延迟增加 50%、数据完整性或安全问题,应立即回滚。 + +## 应用与数据库回滚 + +发布失败时固定部署脚本会恢复上一组 API/Web digest 并重复健康检查。人工回滚只选择服务器已有 root-only 发布清单的历史 Git SHA: + +```bash +sudo /usr/local/sbin/easyai-ai-gateway-release <40 位历史 Git SHA> +``` + +没有 `/root/easyai-ai-gateway-deploy/.releases/.env` 的远端 SHA Tag 不应被盲目信任。数据库备份位于 `/var/backups/easyai-ai-gateway`,只保留最近 5 份。应用回滚不会自动恢复数据库;只有停止写入并再次保留当前副本后,操作员才能选择备份执行 `pg_restore`。 + +## Runner 故障恢复 + +先在 Gitea 确认没有运行中的 Job,再检查服务与两个 daemon: + +```bash +systemctl status easyai-gateway-ci-v2-runner.service --no-pager +docker logs --tail 100 easyai-gateway-ci-v2-runner +docker exec easyai-gateway-ci-v2-runner docker info +df -h /var/lib/docker +``` + +确认空闲后才执行: + +```bash +systemctl restart easyai-gateway-ci-v2-runner.service +journalctl -u easyai-gateway-ci-v2-runner.service -n 100 --no-pager +``` + +不要删除 `easyai-gateway-ci-v2-data` volume;它包含 Runner 注册状态和内层 daemon 缓存。不得用全局 prune 处理磁盘压力。 diff --git a/package.json b/package.json index 48941e1..e4ac4da 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dotenv-cli": "^11.0.0", "nx": "^21.0.0", "typescript": "^5.8.0", - "vite": "^7.0.0" + "vite": "^7.3.5", + "vitest": "3.2.6" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fa16b4..0918eae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,16 +4,29 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@babel/core@<=7.29.0': 7.29.6 + dompurify@<=3.4.10: 3.4.11 + esbuild@>=0.27.3 <0.28.1: 0.28.1 + form-data@>=4.0.0 <4.0.6: 4.0.6 + js-yaml@<3.15.0: 4.1.1 + mermaid@>=11.0.0-alpha.1 <=11.14.0: 11.15.0 + minimatch@>=9.0.0 <9.0.7: 9.0.7 + '@nx/js>picomatch': 4.0.5 + '@nx/vite>picomatch': 4.0.5 + '@nx/workspace>picomatch': 4.0.5 + tmp@<0.2.7: 0.2.7 + importers: .: devDependencies: '@nx/vite': specifier: ^21.0.0 - version: 21.6.11(@babel/traverse@7.29.0)(nx@21.6.11)(typescript@5.9.3)(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))(vitest@3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) + version: 21.6.11(@babel/traverse@7.29.0)(nx@21.6.11)(typescript@5.9.3)(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))(vitest@3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) '@vitejs/plugin-react': specifier: ^5.0.0 - version: 5.2.0(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) + version: 5.2.0(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) dotenv-cli: specifier: ^11.0.0 version: 11.0.0 @@ -24,8 +37,11 @@ importers: specifier: ^5.8.0 version: 5.9.3 vite: - specifier: ^7.0.0 - version: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + specifier: ^7.3.5 + version: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vitest: + specifier: 3.2.6 + version: 3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) apps/web: dependencies: @@ -67,7 +83,7 @@ importers: version: 1.0.2(react@19.2.6) '@vitejs/plugin-react': specifier: ^5.0.0 - version: 5.2.0(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) + version: 5.2.0(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) antd: specifier: ^5.29.3 version: 5.29.3(date-fns@4.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -106,11 +122,11 @@ importers: version: 3.5.0 vite: specifier: ^7.0.0 - version: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + version: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) devDependencies: '@tailwindcss/vite': specifier: ^4.3.0 - version: 4.3.0(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) + version: 4.3.0(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) '@types/react': specifier: ^19.0.0 version: 19.2.14 @@ -124,8 +140,8 @@ importers: specifier: ^5.8.0 version: 5.9.3 vitest: - specifier: 3.2.4 - version: 3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + specifier: 3.2.6 + version: 3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) packages/contracts: devDependencies: @@ -251,18 +267,26 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.3': resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/core@7.29.6': + resolution: {integrity: sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==} engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -275,18 +299,18 @@ packages: resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-create-regexp-features-plugin@7.28.5': resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-define-polyfill-provider@0.6.8': resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} @@ -304,7 +328,7 @@ packages: resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} @@ -318,13 +342,13 @@ packages: resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-replace-supers@7.28.6': resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/helper-skip-transparent-expression-wrappers@7.27.1': resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} @@ -334,10 +358,18 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -346,8 +378,8 @@ packages: resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} '@babel/parser@7.29.3': @@ -355,436 +387,441 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.13.0 + '@babel/core': 7.29.6 '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-proposal-decorators@7.29.0': resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-decorators@7.28.6': resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-import-assertions@7.28.6': resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-import-attributes@7.28.6': resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-typescript@7.28.6': resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-transform-arrow-functions@7.27.1': resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-async-generator-functions@7.29.0': resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-async-to-generator@7.28.6': resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-block-scoped-functions@7.27.1': resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-block-scoping@7.28.6': resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-class-properties@7.28.6': resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-class-static-block@7.28.6': resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.12.0 + '@babel/core': 7.29.6 '@babel/plugin-transform-classes@7.28.6': resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-computed-properties@7.28.6': resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-destructuring@7.28.5': resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-dotall-regex@7.28.6': resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-duplicate-keys@7.27.1': resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-transform-dynamic-import@7.27.1': resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-explicit-resource-management@7.28.6': resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-exponentiation-operator@7.28.6': resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-export-namespace-from@7.27.1': resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-for-of@7.27.1': resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-function-name@7.27.1': resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-json-strings@7.28.6': resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-literals@7.27.1': resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-logical-assignment-operators@7.28.6': resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-member-expression-literals@7.27.1': resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-modules-amd@7.27.1': resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-modules-commonjs@7.28.6': resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-modules-systemjs@7.29.4': resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-modules-umd@7.27.1': resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-transform-new-target@7.27.1': resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-numeric-separator@7.28.6': resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-object-rest-spread@7.28.6': resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-object-super@7.27.1': resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-optional-catch-binding@7.28.6': resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-optional-chaining@7.28.6': resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-parameters@7.27.7': resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-private-methods@7.28.6': resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-private-property-in-object@7.28.6': resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-property-literals@7.27.1': resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-react-jsx-source@7.27.1': resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-regenerator@7.29.0': resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-regexp-modifiers@7.28.6': resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/plugin-transform-reserved-words@7.27.1': resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-runtime@7.29.0': resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-shorthand-properties@7.27.1': resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-spread@7.28.6': resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-sticky-regex@7.27.1': resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-template-literals@7.27.1': resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-typeof-symbol@7.27.1': resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-typescript@7.28.6': resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-unicode-escapes@7.27.1': resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-unicode-property-regex@7.28.6': resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-unicode-regex@7.27.1': resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/plugin-transform-unicode-sets-regex@7.28.6': resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': 7.29.6 '@babel/preset-env@7.29.5': resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/preset-modules@0.1.6-no-external-plugins': resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 '@babel/preset-typescript@7.28.5': resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} @@ -794,6 +831,10 @@ packages: resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} @@ -802,23 +843,15 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@chevrotain/cst-dts-gen@12.0.0': - resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==} - - '@chevrotain/gast@12.0.0': - resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==} - - '@chevrotain/regexp-to-ast@12.0.0': - resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==} - - '@chevrotain/types@12.0.0': - resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==} - - '@chevrotain/utils@12.0.0': - resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} @@ -838,158 +871,158 @@ packages: '@emotion/unitless@0.7.5': resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1043,8 +1076,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mermaid-js/parser@1.1.0': - resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} @@ -1931,128 +1964,128 @@ packages: '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.3': - resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.3': - resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.3': - resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.3': - resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.3': - resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.60.3': - resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.60.3': - resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.60.3': - resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.60.3': - resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.60.3': - resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.60.3': - resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.60.3': - resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.3': - resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.3': - resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.60.3': - resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.3': - resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.3': - resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.3': - resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.3': - resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.3': - resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} cpu: [x64] os: [win32] @@ -2313,9 +2346,6 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -2366,11 +2396,11 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@3.2.6': + resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/mocker@3.2.6': + resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -2380,20 +2410,23 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@3.2.6': + resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/runner@3.2.6': + resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/snapshot@3.2.6': + resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/spy@3.2.6': + resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + + '@vitest/utils@3.2.6': + resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -2435,9 +2468,6 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2467,7 +2497,7 @@ packages: babel-plugin-const-enum@1.2.0: resolution: {integrity: sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': 7.29.6 babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} @@ -2476,27 +2506,27 @@ packages: babel-plugin-polyfill-corejs2@0.4.17: resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 babel-plugin-polyfill-corejs3@0.13.0: resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 babel-plugin-polyfill-corejs3@0.14.2: resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 babel-plugin-polyfill-regenerator@0.6.8: resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/core': 7.29.6 babel-plugin-transform-typescript-metadata@0.3.2: resolution: {integrity: sha512-mWEvCQTgXQf48yDqgN7CH50waTyYBeP2Lpqx4nNWab9sxEpdXVeKgfj1qYI2/TgUPQtNFZ85i3PemRtnXVYYJg==} peerDependencies: - '@babel/core': ^7 + '@babel/core': 7.29.6 '@babel/traverse': ^7 peerDependenciesMeta: '@babel/traverse': @@ -2508,6 +2538,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2522,6 +2556,10 @@ packages: brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} + browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2575,15 +2613,6 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - chevrotain-allstar@0.4.3: - resolution: {integrity: sha512-2X4mkroolSMKqW+H22pyPMUVDqYZzPhephTmg/NODKb1IGYPHfxfhcW0EjS7wcPJNbze2i4vBWT7zT5FKF2lrQ==} - peerDependencies: - chevrotain: ^12.0.0 - - chevrotain@12.0.0: - resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} - engines: {node: '>=22.0.0'} - class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2876,8 +2905,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dompurify@3.4.2: - resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} + dompurify@3.4.11: + resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} dotenv-cli@11.0.0: resolution: {integrity: sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==} @@ -2951,8 +2980,11 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -2968,11 +3000,6 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -3033,8 +3060,8 @@ packages: debug: optional: true - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} front-matter@4.0.2: @@ -3101,6 +3128,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hast-util-from-dom@5.0.1: resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} @@ -3250,8 +3281,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: @@ -3283,10 +3314,6 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - langium@4.2.3: - resolution: {integrity: sha512-sOPIi4hISFnY7twwV97ca1TsxpBtXq0URu/LL1AvxwccPG/RIBBlKS7a/f/EL6w8lTNaS0EFs/F+IdSOaqYpng==} - engines: {node: '>=20.10.0', npm: '>=10.2.3'} - layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -3465,8 +3492,8 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - mermaid@11.14.0: - resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} + mermaid@11.15.0: + resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3600,8 +3627,8 @@ packages: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + minimatch@9.0.7: + resolution: {integrity: sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -3708,22 +3735,22 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} points-on-path@0.2.1: resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} engines: {node: ^10 || ^12 || >=14} pretty-format@30.4.1: @@ -4155,8 +4182,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rollup@4.60.3: - resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4221,9 +4248,6 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -4309,6 +4333,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4321,8 +4349,8 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} toggle-selection@1.0.6: @@ -4482,8 +4510,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4522,16 +4550,16 @@ packages: yaml: optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + vitest@3.2.6: + resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true 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 + '@vitest/browser': 3.2.6 + '@vitest/ui': 3.2.6 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4550,26 +4578,6 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -4773,15 +4781,21 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.29.3': {} - '@babel/core@7.29.0': + '@babel/core@7.29.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 + '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) + '@babel/helpers': 7.29.7 '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 @@ -4803,6 +4817,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.29.0 @@ -4815,29 +4837,29 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.6) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 @@ -4862,9 +4884,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 @@ -4877,18 +4899,18 @@ snapshots: '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.29.0 @@ -4904,8 +4926,12 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.6': @@ -4916,560 +4942,564 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helpers@7.29.2': + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + '@babel/parser@7.29.7': dependencies: - '@babel/core': 7.29.0 + '@babel/types': 7.29.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.6)': + dependencies: + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.6) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.6) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.6) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.6) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.6) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.6) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.6) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.29.5(@babel/core@7.29.0)': + '@babel/preset-env@7.29.5(@babel/core@7.29.6)': dependencies: '@babel/compat-data': 7.29.3 - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.6) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.6) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.6) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.6) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.6) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.6) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.6) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.6) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.6) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.6) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.6) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.6) core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.6)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.6) transitivePeerDependencies: - supports-color @@ -5481,6 +5511,12 @@ snapshots: '@babel/parser': 7.29.3 '@babel/types': 7.29.0 + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -5498,22 +5534,14 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@braintree/sanitize-url@7.1.2': {} - '@chevrotain/cst-dts-gen@12.0.0': - dependencies: - '@chevrotain/gast': 12.0.0 - '@chevrotain/types': 12.0.0 - - '@chevrotain/gast@12.0.0': - dependencies: - '@chevrotain/types': 12.0.0 - - '@chevrotain/regexp-to-ast@12.0.0': {} - - '@chevrotain/types@12.0.0': {} - - '@chevrotain/utils@12.0.0': {} + '@chevrotain/types@11.1.2': {} '@date-fns/tz@1.4.1': {} @@ -5534,82 +5562,82 @@ snapshots: '@emotion/unitless@0.7.5': {} - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/win32-x64@0.28.1': optional: true '@floating-ui/core@1.7.5': @@ -5664,9 +5692,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mermaid-js/parser@1.1.0': + '@mermaid-js/parser@1.2.0': dependencies: - langium: 4.2.3 + '@chevrotain/types': 11.1.2 '@napi-rs/wasm-runtime@0.2.4': dependencies: @@ -5679,7 +5707,7 @@ snapshots: ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 - minimatch: 9.0.3 + minimatch: 9.0.7 nx: 21.6.11 semver: 7.8.0 tslib: 2.8.1 @@ -5687,19 +5715,19 @@ snapshots: '@nx/js@21.6.11(@babel/traverse@7.29.0)(nx@21.6.11)': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) - '@babel/preset-env': 7.29.5(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.6) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.6) + '@babel/preset-env': 7.29.5(@babel/core@7.29.6) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.6) '@babel/runtime': 7.29.2 '@nx/devkit': 21.6.11(nx@21.6.11) '@nx/workspace': 21.6.11 '@zkochan/js-yaml': 0.0.7 - babel-plugin-const-enum: 1.2.0(@babel/core@7.29.0) + babel-plugin-const-enum: 1.2.0(@babel/core@7.29.6) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.0)(@babel/traverse@7.29.0) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.29.6)(@babel/traverse@7.29.0) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.6.1 @@ -5711,7 +5739,7 @@ snapshots: npm-run-path: 4.0.1 ora: 5.3.0 picocolors: 1.1.1 - picomatch: 4.0.2 + picomatch: 4.0.5 semver: 7.8.0 source-map-support: 0.5.19 tinyglobby: 0.2.16 @@ -5754,19 +5782,19 @@ snapshots: '@nx/nx-win32-x64-msvc@21.6.11': optional: true - '@nx/vite@21.6.11(@babel/traverse@7.29.0)(nx@21.6.11)(typescript@5.9.3)(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))(vitest@3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': + '@nx/vite@21.6.11(@babel/traverse@7.29.0)(nx@21.6.11)(typescript@5.9.3)(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))(vitest@3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': dependencies: '@nx/devkit': 21.6.11(nx@21.6.11) '@nx/js': 21.6.11(@babel/traverse@7.29.0)(nx@21.6.11) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.3) ajv: 8.20.0 enquirer: 2.3.6 - picomatch: 4.0.2 + picomatch: 4.0.5 semver: 7.8.0 tsconfig-paths: 4.2.0 tslib: 2.8.1 - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) - vitest: 3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vitest: 3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -5784,7 +5812,7 @@ snapshots: chalk: 4.1.2 enquirer: 2.3.6 nx: 21.6.11 - picomatch: 4.0.2 + picomatch: 4.0.5 semver: 7.8.0 tslib: 2.8.1 yargs-parser: 21.1.1 @@ -6654,79 +6682,79 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.3': {} - '@rollup/rollup-android-arm-eabi@4.60.3': + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true - '@rollup/rollup-android-arm64@4.60.3': + '@rollup/rollup-android-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-arm64@4.60.3': + '@rollup/rollup-darwin-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-x64@4.60.3': + '@rollup/rollup-darwin-x64@4.62.2': optional: true - '@rollup/rollup-freebsd-arm64@4.60.3': + '@rollup/rollup-freebsd-arm64@4.62.2': optional: true - '@rollup/rollup-freebsd-x64@4.60.3': + '@rollup/rollup-freebsd-x64@4.62.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.3': + '@rollup/rollup-linux-arm-musleabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.3': + '@rollup/rollup-linux-arm64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.3': + '@rollup/rollup-linux-arm64-musl@4.62.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.3': + '@rollup/rollup-linux-loong64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.3': + '@rollup/rollup-linux-loong64-musl@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.3': + '@rollup/rollup-linux-ppc64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.3': + '@rollup/rollup-linux-ppc64-musl@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.3': + '@rollup/rollup-linux-riscv64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.3': + '@rollup/rollup-linux-riscv64-musl@4.62.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.3': + '@rollup/rollup-linux-s390x-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.3': + '@rollup/rollup-linux-x64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-musl@4.60.3': + '@rollup/rollup-linux-x64-musl@4.62.2': optional: true - '@rollup/rollup-openbsd-x64@4.60.3': + '@rollup/rollup-openbsd-x64@4.62.2': optional: true - '@rollup/rollup-openharmony-arm64@4.60.3': + '@rollup/rollup-openharmony-arm64@4.62.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.3': + '@rollup/rollup-win32-arm64-msvc@4.62.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.3': + '@rollup/rollup-win32-ia32-msvc@4.62.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.3': + '@rollup/rollup-win32-x64-gnu@4.62.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.3': + '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true '@shikijs/core@3.23.0': @@ -6794,7 +6822,7 @@ snapshots: '@streamdown/mermaid@1.0.2(react@19.2.6)': dependencies: - mermaid: 11.14.0 + mermaid: 11.15.0 react: 19.2.6 '@tailwindcss/node@4.3.0': @@ -6858,12 +6886,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.3.0(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': + '@tailwindcss/vite@4.3.0(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': dependencies: '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 tailwindcss: 4.3.0 - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) '@tybys/wasm-util@0.9.0': dependencies: @@ -7022,8 +7050,6 @@ snapshots: dependencies: '@types/estree': 1.0.9 - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} '@types/geojson@7946.0.16': {} @@ -7064,57 +7090,61 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-react@5.2.0(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': + '@vitejs/plugin-react@5.2.0(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.6) '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.4': + '@vitest/expect@3.2.6': dependencies: '@types/chai': 5.2.3 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': + '@vitest/mocker@3.2.6(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 3.2.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) - '@vitest/pretty-format@3.2.4': + '@vitest/pretty-format@3.2.6': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.2.4': + '@vitest/pretty-format@3.2.7': dependencies: - '@vitest/utils': 3.2.4 + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.6': + dependencies: + '@vitest/utils': 3.2.6 pathe: 2.0.3 strip-literal: 3.1.0 - '@vitest/snapshot@3.2.4': + '@vitest/snapshot@3.2.6': dependencies: - '@vitest/pretty-format': 3.2.4 + '@vitest/pretty-format': 3.2.6 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@3.2.4': + '@vitest/spy@3.2.6': dependencies: tinyspy: 4.0.4 - '@vitest/utils@3.2.4': + '@vitest/utils@3.2.6': dependencies: - '@vitest/pretty-format': 3.2.4 + '@vitest/pretty-format': 3.2.6 loupe: 3.2.1 tinyrainbow: 2.0.0 @@ -7122,7 +7152,7 @@ snapshots: '@yarnpkg/parsers@3.0.2': dependencies: - js-yaml: 3.14.2 + js-yaml: 4.1.1 tslib: 2.8.1 '@zkochan/js-yaml@0.0.7': @@ -7206,10 +7236,6 @@ snapshots: - luxon - moment - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - argparse@2.0.1: {} aria-hidden@1.2.6: @@ -7235,16 +7261,16 @@ snapshots: axios@1.16.0: dependencies: follow-redirects: 1.16.0 - form-data: 4.0.5 + form-data: 4.0.6 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug - babel-plugin-const-enum@1.2.0(@babel/core@7.29.0): + babel-plugin-const-enum@1.2.0(@babel/core@7.29.6): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.6) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -7255,41 +7281,41 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.12 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.6): dependencies: '@babel/compat-data': 7.29.3 - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.6) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.6): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.6) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.6): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.6) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.6): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.6 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.6) transitivePeerDependencies: - supports-color - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.0)(@babel/traverse@7.29.0): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.29.6)(@babel/traverse@7.29.0): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.6 '@babel/helper-plugin-utils': 7.28.6 optionalDependencies: '@babel/traverse': 7.29.0 @@ -7298,6 +7324,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} baseline-browser-mapping@2.10.28: {} @@ -7312,6 +7340,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.7: + dependencies: + balanced-match: 4.0.4 + browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.28 @@ -7363,19 +7395,6 @@ snapshots: check-error@2.1.3: {} - chevrotain-allstar@0.4.3(chevrotain@12.0.0): - dependencies: - chevrotain: 12.0.0 - lodash-es: 4.18.1 - - chevrotain@12.0.0: - dependencies: - '@chevrotain/cst-dts-gen': 12.0.0 - '@chevrotain/gast': 12.0.0 - '@chevrotain/regexp-to-ast': 12.0.0 - '@chevrotain/types': 12.0.0 - '@chevrotain/utils': 12.0.0 - class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -7682,7 +7701,7 @@ snapshots: dependencies: dequal: 2.0.3 - dompurify@3.4.2: + dompurify@3.4.11: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -7753,36 +7772,38 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 - esbuild@0.27.7: + es-toolkit@1.49.0: {} + + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 escalade@3.2.0: {} @@ -7790,8 +7811,6 @@ snapshots: escape-string-regexp@5.0.0: {} - esprima@4.0.1: {} - esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -7818,6 +7837,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -7830,17 +7853,17 @@ snapshots: follow-redirects@1.16.0: {} - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 front-matter@4.0.2: dependencies: - js-yaml: 3.14.2 + js-yaml: 4.1.1 fs-constants@1.0.0: {} @@ -7865,7 +7888,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -7893,6 +7916,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hast-util-from-dom@5.0.1: dependencies: '@types/hast': 3.0.4 @@ -8102,10 +8129,9 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@3.14.2: + js-yaml@4.1.1: dependencies: - argparse: 1.0.10 - esprima: 4.0.1 + argparse: 2.0.1 jsesc@3.1.0: {} @@ -8127,15 +8153,6 @@ snapshots: khroma@2.1.0: {} - langium@4.2.3: - dependencies: - '@chevrotain/regexp-to-ast': 12.0.0 - chevrotain: 12.0.0 - chevrotain-allstar: 0.4.3(chevrotain@12.0.0) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -8393,11 +8410,11 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - mermaid@11.14.0: + mermaid@11.15.0: dependencies: '@braintree/sanitize-url': 7.1.2 '@iconify/utils': 3.1.3 - '@mermaid-js/parser': 1.1.0 + '@mermaid-js/parser': 1.2.0 '@types/d3': 7.4.3 '@upsetjs/venn.js': 2.0.0 cytoscape: 3.33.3 @@ -8407,10 +8424,10 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.4.2 + dompurify: 3.4.11 + es-toolkit: 1.49.0 katex: 0.16.45 khroma: 2.1.0 - lodash-es: 4.18.1 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.4.0 @@ -8662,9 +8679,9 @@ snapshots: dependencies: brace-expansion: 2.1.0 - minimatch@9.0.3: + minimatch@9.0.7: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 5.0.7 minimist@1.2.8: {} @@ -8710,7 +8727,7 @@ snapshots: jest-diff: 30.4.1 jsonc-parser: 3.2.0 lines-and-columns: 2.0.3 - minimatch: 9.0.3 + minimatch: 9.0.7 node-machine-id: 1.1.12 npm-run-path: 4.0.1 open: 8.4.2 @@ -8719,7 +8736,7 @@ snapshots: semver: 7.8.0 string-width: 4.2.3 tar-stream: 2.2.0 - tmp: 0.2.5 + tmp: 0.2.7 tree-kill: 1.2.2 tsconfig-paths: 4.2.0 tslib: 2.8.1 @@ -8814,10 +8831,10 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -8825,7 +8842,7 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - postcss@8.5.14: + postcss@8.5.19: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -9433,35 +9450,35 @@ snapshots: robust-predicates@3.0.3: {} - rollup@4.60.3: + rollup@4.62.2: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.3 - '@rollup/rollup-android-arm64': 4.60.3 - '@rollup/rollup-darwin-arm64': 4.60.3 - '@rollup/rollup-darwin-x64': 4.60.3 - '@rollup/rollup-freebsd-arm64': 4.60.3 - '@rollup/rollup-freebsd-x64': 4.60.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 - '@rollup/rollup-linux-arm-musleabihf': 4.60.3 - '@rollup/rollup-linux-arm64-gnu': 4.60.3 - '@rollup/rollup-linux-arm64-musl': 4.60.3 - '@rollup/rollup-linux-loong64-gnu': 4.60.3 - '@rollup/rollup-linux-loong64-musl': 4.60.3 - '@rollup/rollup-linux-ppc64-gnu': 4.60.3 - '@rollup/rollup-linux-ppc64-musl': 4.60.3 - '@rollup/rollup-linux-riscv64-gnu': 4.60.3 - '@rollup/rollup-linux-riscv64-musl': 4.60.3 - '@rollup/rollup-linux-s390x-gnu': 4.60.3 - '@rollup/rollup-linux-x64-gnu': 4.60.3 - '@rollup/rollup-linux-x64-musl': 4.60.3 - '@rollup/rollup-openbsd-x64': 4.60.3 - '@rollup/rollup-openharmony-arm64': 4.60.3 - '@rollup/rollup-win32-arm64-msvc': 4.60.3 - '@rollup/rollup-win32-ia32-msvc': 4.60.3 - '@rollup/rollup-win32-x64-gnu': 4.60.3 - '@rollup/rollup-win32-x64-msvc': 4.60.3 + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 roughjs@4.6.6: @@ -9521,8 +9538,6 @@ snapshots: space-separated-tokens@2.0.2: {} - sprintf-js@1.0.3: {} - stackback@0.0.2: {} std-env@3.10.0: {} @@ -9533,7 +9548,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 marked: 17.0.6 - mermaid: 11.14.0 + mermaid: 11.15.0 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) rehype-harden: 1.1.8 @@ -9617,8 +9632,13 @@ snapshots: tinyglobby@0.2.16: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinypool@1.1.1: {} @@ -9626,7 +9646,7 @@ snapshots: tinyspy@4.0.4: {} - tmp@0.2.5: {} + tmp@0.2.7: {} toggle-selection@1.0.6: {} @@ -9777,7 +9797,7 @@ snapshots: debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) transitivePeerDependencies: - '@types/node' - jiti @@ -9792,43 +9812,43 @@ snapshots: - tsx - yaml - vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4): + vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4): dependencies: - esbuild: 0.27.7 + esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.14 - rollup: 4.60.3 - tinyglobby: 0.2.16 + postcss: 8.5.19 + rollup: 4.62.2 + tinyglobby: 0.2.17 optionalDependencies: fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.32.0 yaml: 2.8.4 - vitest@3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4): + vitest@3.2.6(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4): dependencies: '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.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 + '@vitest/expect': 3.2.6 + '@vitest/mocker': 3.2.6(vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.6 + '@vitest/snapshot': 3.2.6 + '@vitest/spy': 3.2.6 + '@vitest/utils': 3.2.6 chai: 5.3.3 debug: 4.4.3 expect-type: 1.3.0 magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.4 + picomatch: 4.0.5 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.3(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) + vite: 7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) vite-node: 3.2.4(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4) why-is-node-running: 2.3.0 optionalDependencies: @@ -9847,23 +9867,6 @@ snapshots: - tsx - yaml - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.1.0: {} - wcwidth@1.0.1: dependencies: defaults: 1.0.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 68399ab..fea2db6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,20 @@ packages: - apps/web - packages/* + allowBuilds: esbuild: true nx: true + +overrides: + '@babel/core@<=7.29.0': 7.29.6 + 'dompurify@<=3.4.10': 3.4.11 + 'esbuild@>=0.27.3 <0.28.1': 0.28.1 + 'form-data@>=4.0.0 <4.0.6': 4.0.6 + 'js-yaml@<3.15.0': 4.1.1 + 'mermaid@>=11.0.0-alpha.1 <=11.14.0': 11.15.0 + 'minimatch@>=9.0.0 <9.0.7': 9.0.7 + '@nx/js>picomatch': 4.0.5 + '@nx/vite>picomatch': 4.0.5 + '@nx/workspace>picomatch': 4.0.5 + 'tmp@<0.2.7': 0.2.7 diff --git a/scripts/ci-build-images.sh b/scripts/ci-build-images.sh new file mode 100755 index 0000000..7061ca9 --- /dev/null +++ b/scripts/ci-build-images.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +cd "$root" + +tag=${IMAGE_TAG:-} +registry=${AI_GATEWAY_IMAGE_REGISTRY:-registry.cn-shanghai.aliyuncs.com/easyaigc} +builder=${AI_GATEWAY_BUILDX_BUILDER:-easyai-gateway-ci} +cache_limit=${AI_GATEWAY_BUILDX_CACHE_LIMIT:-2gb} +platform=${AI_GATEWAY_PLATFORM:-linux/amd64} +keep_images=0 +build_complete=0 +readonly trivy_db_repository=ghcr.m.daocloud.io/aquasecurity/trivy-db:2 + +case ${1:-} in + '') ;; + --keep-images) keep_images=1 ;; + *) + echo 'usage: ci-build-images.sh [--keep-images]' >&2 + exit 64 + ;; +esac + +if [[ ! $tag =~ ^[0-9a-f]{40}$ ]]; then + echo 'IMAGE_TAG must be a full lowercase Git SHA' >&2 + exit 1 +fi +if [[ ! $registry =~ ^[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._/-]+)+$ ]]; then + echo 'AI_GATEWAY_IMAGE_REGISTRY has an invalid format' >&2 + exit 1 +fi +if [[ ! $cache_limit =~ ^[1-9][0-9]*(kb|mb|gb)$ ]]; then + echo 'AI_GATEWAY_BUILDX_CACHE_LIMIT has an invalid format' >&2 + exit 1 +fi + +api_image="$registry/ai-gateway:$tag" +web_image="$registry/ai-gateway-web:$tag" + +cleanup() { + original_status=$? + trap - EXIT HUP INT TERM + cleanup_status=0 + + docker buildx prune --builder "$builder" --force --max-used-space "$cache_limit" >/dev/null || { + cleanup_status=$? + echo 'failed to prune the CI BuildKit cache' >&2 + } + if [[ $keep_images -ne 1 || $build_complete -ne 1 ]]; then + docker image rm "$api_image" "$web_image" >/dev/null 2>&1 || { + image_rm_status=$? + [[ $cleanup_status -ne 0 ]] || cleanup_status=$image_rm_status + echo 'failed to remove CI image tags' >&2 + } + fi + + if [[ $original_status -ne 0 ]]; then + exit "$original_status" + fi + exit "$cleanup_status" +} +trap cleanup EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM + +if ! docker buildx inspect "$builder" >/dev/null 2>&1; then + docker buildx create --name "$builder" --driver docker-container --use >/dev/null +fi +docker buildx inspect "$builder" --bootstrap >/dev/null +common_args=( + --builder "$builder" + --platform "$platform" + --file Dockerfile + --pull + --load +) + +docker buildx build --builder "$builder" \ + "${common_args[@]:2}" \ + --target api \ + --build-arg "GO_BUILD_IMAGE=${AI_GATEWAY_GO_BUILD_IMAGE:-docker.m.daocloud.io/library/golang:1.26.5-alpine}" \ + --build-arg "API_RUNTIME_IMAGE=${AI_GATEWAY_API_RUNTIME_IMAGE:-docker.m.daocloud.io/library/alpine:3.22}" \ + --tag "$api_image" . + +docker buildx build --builder "$builder" \ + "${common_args[@]:2}" \ + --target web \ + --build-arg "NODE_BUILD_IMAGE=${AI_GATEWAY_NODE_BUILD_IMAGE:-docker.m.daocloud.io/library/node:24-alpine}" \ + --build-arg "WEB_RUNTIME_IMAGE=${AI_GATEWAY_WEB_RUNTIME_IMAGE:-docker.m.daocloud.io/library/nginx:1.27-alpine}" \ + --tag "$web_image" . + +for image in "$api_image" "$web_image"; do + trivy image --db-repository "$trivy_db_repository" \ + --scanners vuln,secret --severity HIGH,CRITICAL --ignore-unfixed \ + --exit-code 1 --timeout 15m "$image" +done + +build_complete=1 +printf 'gateway_images=PASS git_sha=%s api=%s web=%s\n' "$tag" "$api_image" "$web_image" diff --git a/scripts/ci-validate-migrations.mjs b/scripts/ci-validate-migrations.mjs new file mode 100755 index 0000000..b75b309 --- /dev/null +++ b/scripts/ci-validate-migrations.mjs @@ -0,0 +1,435 @@ +#!/usr/bin/env node + +import { execFileSync, spawnSync } from 'node:child_process' +import { lstatSync, readFileSync } from 'node:fs' +import { basename } from 'node:path' + +const migrationDirectory = 'apps/api/migrations/' +const baselineFile = 'deploy/ci/production-migration-base' +const base = process.argv[2] || process.env.PRODUCTION_MIGRATION_BASE_SHA || '' +const immutableRef = + process.argv[3] || process.env.MIGRATION_IMMUTABILITY_REF || '' + +function fail(message) { + console.error(`migration_safety=FAIL ${message}`) + process.exit(1) +} + +function git(args, options = {}) { + try { + return execFileSync('git', args, { + encoding: options.encoding ?? 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }) + } catch (error) { + const stderr = error.stderr?.toString().trim() + fail(stderr ? `git_error=${JSON.stringify(stderr)}` : 'git command failed') + } +} + +if (!/^[0-9a-f]{40}$/.test(base)) { + fail('PRODUCTION_MIGRATION_BASE_SHA must be a full lowercase commit SHA') +} + +const root = git(['rev-parse', '--show-toplevel']).trim() +process.chdir(root) + +const resolvedBase = git([ + 'rev-parse', + '--verify', + '--end-of-options', + `${base}^{commit}`, +]).trim() +if (resolvedBase !== base) { + fail('production migration baseline did not resolve exactly') +} + +const ancestry = spawnSync('git', ['merge-base', '--is-ancestor', base, 'HEAD'], { + stdio: 'ignore', +}) +if (ancestry.status !== 0) { + fail('production migration baseline is not an ancestor of HEAD') +} + +const head = git(['rev-parse', '--verify', 'HEAD^{commit}']).trim() + +const baselineTreeEntry = git(['ls-tree', 'HEAD', '--', baselineFile]).trim() +let baselineState +try { + baselineState = lstatSync(baselineFile) +} catch { + fail('production migration baseline file is unavailable in the worktree') +} +if ( + !/^100644 blob [0-9a-f]+\t/.test(baselineTreeEntry) || + !baselineState.isFile() || + baselineState.isSymbolicLink() +) { + fail('production migration baseline must be a mode 100644 regular file') +} +if (readFileSync(baselineFile, 'utf8').trim() !== base) { + fail('production migration baseline argument does not match the reviewed file') +} + +function changedMigrations(from) { + const rawChanges = git( + [ + 'diff', + '--name-status', + '-z', + '--no-renames', + `${from}..HEAD`, + '--', + migrationDirectory, + ], + { encoding: null }, + ) + const fields = rawChanges.toString('utf8').split('\0') + if (fields.at(-1) === '') fields.pop() + if (fields.length % 2 !== 0) { + fail('could not parse changed migration paths') + } + return fields +} + +const fields = changedMigrations(base) + +function executableSql(sql, source = 'SQL input') { + let output = '' + + for (let index = 0; index < sql.length; index += 1) { + const current = sql[index] + const next = sql[index + 1] + + if (current === '-' && next === '-') { + index += 2 + while (index < sql.length && sql[index] !== '\n') index += 1 + output += '\n' + continue + } + + if (current === '/' && next === '*') { + let depth = 1 + index += 2 + while (index < sql.length && depth > 0) { + if (sql[index] === '/' && sql[index + 1] === '*') { + depth += 1 + index += 2 + continue + } + if (sql[index] === '*' && sql[index + 1] === '/') { + depth -= 1 + index += 2 + continue + } + index += 1 + } + if (depth !== 0) fail('unterminated SQL block comment') + index -= 1 + output += ' ' + continue + } + + if (current === "'" || current === '"') { + const prefix = sql.slice(Math.max(0, index - 2), index).toUpperCase() + const previous = sql[index - 1]?.toUpperCase() + const beforePrevious = sql[index - 2] || '' + const beforeUnicodePrefix = sql[index - 3] || '' + const escapeString = + current === "'" && + previous === 'E' && + !/[A-Za-z0-9_$]/.test(beforePrevious) + const unicodeString = + prefix === 'U&' && !/[A-Za-z0-9_$]/.test(beforeUnicodePrefix) + if (escapeString || unicodeString) { + fail(`${source}: PostgreSQL escape/unicode strings are not allowed in migrations`) + } + const quote = current + index += 1 + let closed = false + while (index < sql.length) { + if (sql[index] === quote && sql[index + 1] === quote) { + index += 2 + continue + } + if (sql[index] === quote) { + closed = true + break + } + index += 1 + } + if (!closed) fail('unterminated SQL quoted value') + output += ' ' + continue + } + + if (current === '$') { + const delimiter = sql.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/)?.[0] + if (delimiter) { + const bodyStart = index + delimiter.length + const bodyEnd = sql.indexOf(delimiter, bodyStart) + if (bodyEnd === -1) fail('unterminated SQL dollar-quoted body') + // PostgreSQL function bodies may contain executable DDL. Scan their + // contents recursively instead of treating them as inert strings. + const bodySql = executableSql(sql.slice(bodyStart, bodyEnd), source) + // A bare END terminates a PL/pgSQL block inside a dollar-quoted body, + // but at the top level PostgreSQL treats END as COMMIT. Mask only the + // block terminator here so the top-level transaction rule stays exact. + const bodyWithoutBlockEnd = bodySql + .split(';') + .map((statement) => (/^\s*END\s*$/i.test(statement) ? ' ' : statement)) + .join(';') + output += ` ${bodyWithoutBlockEnd} ` + index = bodyEnd + delimiter.length - 1 + continue + } + } + + output += current + } + + return output +} + +const destructiveRules = [ + { + name: 'procedural SQL body', + pattern: + /^\s*(?:DO\b|CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\b)/i, + }, + { + name: 'dynamic SQL execution', + pattern: /\bEXECUTE\b/i, + }, + { + name: 'destructive DROP operation', + pattern: /\bDROP\b/i, + }, + { name: 'MERGE operation', pattern: /\bMERGE\b/i }, + { + name: 'CREATE OR REPLACE operation', + pattern: /\bCREATE\s+OR\s+REPLACE\b/i, + }, + { name: 'TRUNCATE operation', pattern: /\bTRUNCATE(?:\s+TABLE)?\b/i }, + { name: 'DELETE FROM operation', pattern: /\bDELETE\s+FROM\b/i }, + { name: 'permission revocation', pattern: /\bREVOKE\b/i }, + { + name: 'transaction control operation', + pattern: + /\b(?:COMMIT|ROLLBACK|ABORT|SAVEPOINT)\b|\bRELEASE\s+SAVEPOINT\b|\bPREPARE\s+TRANSACTION\b|^\s*END(?:\s+(?:WORK|TRANSACTION))?(?:\s+AND\s+(?:NO\s+)?CHAIN)?\s*$/i, + }, + { + name: 'SQL string semantics override', + pattern: + /\bSET\s+(?:LOCAL\s+|SESSION\s+)?STANDARD_CONFORMING_STRINGS\b/i, + }, + { + name: 'object or column rename', + pattern: /\bRENAME\b/i, + }, + { name: 'SET SCHEMA operation', pattern: /\bSET\s+SCHEMA\b/i }, + { + name: 'column type change', + pattern: + /\bALTER\s+TABLE\b[\s\S]*\bALTER\s+(?:COLUMN\s+)?[\s\S]*\b(?:SET\s+DATA\s+)?TYPE\b/i, + }, + { + name: 'SET NOT NULL operation', + pattern: + /\bALTER\s+TABLE\b[\s\S]*\bALTER\s+(?:COLUMN\s+)?[\s\S]*\bSET\s+NOT\s+NULL\b/i, + }, + { + name: 'non-null column addition', + pattern: + /\bALTER\s+TABLE\b[\s\S]*\bADD\s+(?:COLUMN\s+)?[\s\S]*\bNOT\s+NULL\b/i, + }, + { + name: 'DROP DEFAULT operation', + pattern: + /\bALTER\s+TABLE\b[\s\S]*\bALTER\s+(?:COLUMN\s+)?[\s\S]*\bDROP\s+DEFAULT\b/i, + }, +] + +const violations = [] +let addedFiles = 0 +let immutableBase = '' +const orderingCandidates = new Set() + +if (immutableRef) { + if ( + !/^[0-9a-f]{40}$/.test(immutableRef) && + immutableRef !== 'refs/remotes/origin/main' + ) { + fail('MIGRATION_IMMUTABILITY_REF must be a full commit SHA or refs/remotes/origin/main') + } + + const immutableTip = git([ + 'rev-parse', + '--verify', + '--end-of-options', + `${immutableRef}^{commit}`, + ]).trim() + const baseAtEvent = spawnSync( + 'git', + ['show', `${immutableTip}:${baselineFile}`], + { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }, + ) + if (baseAtEvent.status === 0) { + const previousBaselineTreeEntry = git([ + 'ls-tree', + immutableTip, + '--', + baselineFile, + ]).trim() + if (!/^100644 blob [0-9a-f]+\t/.test(previousBaselineTreeEntry)) { + fail('previous production migration baseline must be a mode 100644 regular file') + } + const previousProductionBase = baseAtEvent.stdout.trim() + if (!/^[0-9a-f]{40}$/.test(previousProductionBase)) { + fail('previous production migration baseline is invalid') + } + const baselineAdvance = spawnSync( + 'git', + ['merge-base', '--is-ancestor', previousProductionBase, base], + { stdio: 'ignore' }, + ) + if (baselineAdvance.status !== 0) { + fail('production migration baseline must only move forward') + } + } + const baselineWasAlreadyReviewed = spawnSync( + 'git', + ['merge-base', '--is-ancestor', base, immutableTip], + { stdio: 'ignore' }, + ) + if (baselineWasAlreadyReviewed.status !== 0) { + fail('production migration baseline must belong to the immutable event base history') + } + if (immutableTip === head) { + const parent = spawnSync( + 'git', + ['rev-parse', '--verify', `${immutableTip}^1^{commit}`], + { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }, + ) + immutableBase = parent.status === 0 ? parent.stdout.trim() : '' + } else { + const immutableAncestry = spawnSync( + 'git', + ['merge-base', '--is-ancestor', immutableTip, head], + { stdio: 'ignore' }, + ) + if (immutableAncestry.status !== 0) { + fail('migration immutability base is not an ancestor of HEAD') + } + immutableBase = immutableTip + } + + if (immutableBase) { + const immutableChanges = changedMigrations(immutableBase) + for (let index = 0; index < immutableChanges.length; index += 2) { + const status = immutableChanges[index] + const file = immutableChanges[index + 1] + if (status === 'A') { + orderingCandidates.add(file) + } else { + violations.push( + `${file}: migration present on main is immutable (status ${status})`, + ) + } + } + } +} + +function migrationNamesAt(ref) { + const output = git( + ['ls-tree', '-r', '-z', '--name-only', ref, '--', migrationDirectory], + { encoding: null }, + ) + return output + .toString('utf8') + .split('\0') + .filter(Boolean) + .filter((file) => { + const relative = file.slice(migrationDirectory.length) + return file.startsWith(migrationDirectory) && !relative.includes('/') + }) + .map((file) => basename(file)) + .filter((name) => /^\d{4}_[a-z0-9][a-z0-9_]*\.sql$/.test(name)) + .sort() +} + +const orderingBase = immutableBase || base +const existingNames = migrationNamesAt(orderingBase) +const previousMaximumName = existingNames.at(-1) || '' + +if (!immutableBase) { + for (let index = 0; index < fields.length; index += 2) { + if (fields[index] === 'A') orderingCandidates.add(fields[index + 1]) + } +} + +for (let index = 0; index < fields.length; index += 2) { + const status = fields[index] + const file = fields[index + 1] + + if (status !== 'A') { + violations.push(`${file}: applied migrations are immutable (status ${status})`) + continue + } + + addedFiles += 1 + const filename = basename(file) + if ( + file !== `${migrationDirectory}${filename}` || + !/^\d{4}_[a-z0-9][a-z0-9_]*\.sql$/.test(filename) + ) { + violations.push(`${file}: new migration must use NNNN_lowercase_name.sql`) + continue + } + + if ( + orderingCandidates.has(file) && + previousMaximumName && + filename <= previousMaximumName + ) { + violations.push( + `${file}: new migration name must sort after ${previousMaximumName}`, + ) + continue + } + + const treeEntry = git(['ls-tree', 'HEAD', '--', file]).trim() + let fileState + try { + fileState = lstatSync(file) + } catch { + violations.push(`${file}: new migration is unavailable in the worktree`) + continue + } + if ( + !/^100644 blob [0-9a-f]+\t/.test(treeEntry) || + !fileState.isFile() || + fileState.isSymbolicLink() + ) { + violations.push(`${file}: new migration must be a mode 100644 regular file`) + continue + } + + const statements = executableSql(readFileSync(file, 'utf8'), file).split(';') + for (const statement of statements) { + for (const rule of destructiveRules) { + if (rule.pattern.test(statement)) { + violations.push(`${file}: ${rule.name}`) + } + } + } +} + +if (violations.length > 0) { + for (const violation of [...new Set(violations)]) { + console.error(`migration_safety_violation=${JSON.stringify(violation)}`) + } + fail(`base=${base} violations=${new Set(violations).size}`) +} + +console.log(`migration_safety=PASS base=${base} added_files=${addedFiles}`) diff --git a/scripts/ci-validate-semver.sh b/scripts/ci-validate-semver.sh new file mode 100755 index 0000000..167235f --- /dev/null +++ b/scripts/ci-validate-semver.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +tag=${1:-} + +die() { + printf 'invalid release tag: %s\n' "$tag" >&2 + exit 1 +} + +[[ $tag == v* ]] || die +version=${tag#v} + +# Bash arrays discard trailing empty fields, so parsing first would accept +# values such as v1.2.3. and v1.2.3-alpha. Validate the complete string with +# the SemVer 2.0.0 grammar before doing anything else with the tag. +semver_pattern='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' +LC_ALL=C +export LC_ALL +[[ $version =~ $semver_pattern ]] || die + +printf 'release_tag=PASS tag=%s\n' "$tag" diff --git a/scripts/provision-ci-runner.sh b/scripts/provision-ci-runner.sh new file mode 100755 index 0000000..6426a09 --- /dev/null +++ b/scripts/provision-ci-runner.sh @@ -0,0 +1,401 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +PREFIX=/opt/easyai-gateway-ci +GITEA_INSTANCE_URL=${GITEA_INSTANCE_URL:-https://git.51easyai.com} +RUNNER_NAME=${RUNNER_NAME:-easyai-gateway-ci-unprivileged-v2} +RUNNER_VOLUME=easyai-gateway-ci-v2-data +RUNNER_CONTAINER=easyai-gateway-ci-v2-runner +LEGACY_BUILDER=${AI_GATEWAY_BUILDX_BUILDER:-easyai-gateway-ci} +MIN_FREE_GIB=${CI_RUNNER_MIN_FREE_GIB:-8} +MIN_POST_INSTALL_FREE_GIB=${CI_RUNNER_MIN_POST_INSTALL_FREE_GIB:-4} +RUNNER_CPUS=2 +RUNNER_MEMORY=2g +RUNNER_MEMORY_SWAP=3g +RUNNER_PIDS_LIMIT=1024 + +GITEA_RUNNER_VERSION=2.0.0 +GO_VERSION=1.26.5 +GO_SHA256=5c2c3b16caefa1d968a94c1daca04a7ca301a496d9b086e17ad77bb81393f053 +NODE_VERSION=24.16.0 +NODE_SHA256=d804845d34eddc21dc1092b519d643ef40b1f58ec5dec5c22b1f4bd8fabde6c9 +TRIVY_VERSION=0.70.0 +TRIVY_SHA256=8b4376d5d6befe5c24d503f10ff136d9e0c49f9127a4279fd110b727929a5aa9 +SHELLCHECK_VERSION=0.11.0 +SHELLCHECK_SHA256=8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198 +COMPOSE_VERSION=5.3.1 +COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959 +PNPM_VERSION=10.18.1 +RUNNER_IMAGE='docker.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a' +JOB_IMAGE='docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7' +RUNNER_IMAGE_SOURCE=${CI_RUNNER_IMAGE_SOURCE:-$RUNNER_IMAGE} +NESTED_DOCKER_REGISTRY_MIRROR=${CI_NESTED_DOCKER_REGISTRY_MIRROR:-} +RUNNER_LABEL="easyai-gateway-ci-unprivileged-v2:docker://$JOB_IMAGE" + +fail() { + echo "$*" >&2 + exit 1 +} + +case $MIN_FREE_GIB:$MIN_POST_INSTALL_FREE_GIB in + *[!0-9:]* | :* | *:) + fail "CI runner free-space limits must be whole GiB values" + ;; +esac +[ "$MIN_FREE_GIB" -ge 8 ] || fail "CI_RUNNER_MIN_FREE_GIB cannot be lower than 8" +[ "$MIN_POST_INSTALL_FREE_GIB" -ge 4 ] || \ + fail "CI_RUNNER_MIN_POST_INSTALL_FREE_GIB cannot be lower than 4" + +[ "$(id -u)" -eq 0 ] || fail "run as root" +[ "$(uname -s)-$(uname -m)" = "Linux-x86_64" ] || fail "only Linux x86_64 is supported" +case $RUNNER_IMAGE in + *"/runner:${GITEA_RUNNER_VERSION}-dind-rootless@sha256:"*) ;; + *) fail "RUNNER_IMAGE does not match GITEA_RUNNER_VERSION" ;; +esac +runner_manifest_digest=${RUNNER_IMAGE##*@} +case $RUNNER_IMAGE_SOURCE in + *@"$runner_manifest_digest") ;; + *) fail "CI_RUNNER_IMAGE_SOURCE must use the pinned runner manifest digest" ;; +esac +if [ -n "$NESTED_DOCKER_REGISTRY_MIRROR" ] && \ + ! printf '%s\n' "$NESTED_DOCKER_REGISTRY_MIRROR" | \ + grep -Eq '^https://[A-Za-z0-9.-]+(:[0-9]+)?$'; then + fail "CI_NESTED_DOCKER_REGISTRY_MIRROR must be an HTTPS registry origin" +fi +command -v docker >/dev/null 2>&1 || fail "Docker Engine is required" +docker info >/dev/null 2>&1 || fail "Docker Engine is not reachable" +grep -Fq "\"$RUNNER_LABEL\"" "$ROOT/deploy/ci/act-runner-v2-config.yaml" || \ + fail "runner label does not match the pinned job image" + +# Fail closed before installing anything. Neither the legacy host runner nor an +# earlier v2 unit may poll for work while its replacement is being prepared. +systemctl disable --now easyai-gateway-act-runner.service >/dev/null 2>&1 || true +systemctl disable --now easyai-gateway-ci-v2-runner.service >/dev/null 2>&1 || true +docker rm --force "$RUNNER_CONTAINER" >/dev/null 2>&1 || true +rm -f /etc/systemd/system/easyai-gateway-act-runner.service + +if id easyai-gateway-runner >/dev/null 2>&1; then + if getent group docker >/dev/null 2>&1; then + gpasswd -d easyai-gateway-runner docker >/dev/null 2>&1 || true + fi + if getent group sudo >/dev/null 2>&1; then + gpasswd -d easyai-gateway-runner sudo >/dev/null 2>&1 || true + fi +fi +if id easyai-gateway-ci-v2 >/dev/null 2>&1; then + if getent group docker >/dev/null 2>&1; then + gpasswd -d easyai-gateway-ci-v2 docker >/dev/null 2>&1 || true + fi + if getent group sudo >/dev/null 2>&1; then + gpasswd -d easyai-gateway-ci-v2 sudo >/dev/null 2>&1 || true + fi +fi +for account in easyai-gateway-runner easyai-gateway-ci-v2; do + if id "$account" >/dev/null 2>&1 && \ + id -nG "$account" | tr ' ' '\n' | grep -Eq '^(docker|sudo)$'; then + fail "$account must not belong to a privileged group" + fi +done + +if ! command -v make >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1 || \ + ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1 || \ + ! command -v xz >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends \ + build-essential ca-certificates curl git jq perl unzip util-linux xz-utils +fi + +docker_root=$(docker info --format '{{.DockerRootDir}}') +[ -n "$docker_root" ] || fail "Docker root directory could not be determined" + +require_free_space() { + minimum_gib=$1 + phase=$2 + available_kib=$(df -Pk "$docker_root" | awk 'NR == 2 { print $4 }') + case $available_kib in + '' | *[!0-9]*) fail "free space could not be determined for $docker_root" ;; + esac + minimum_kib=$((minimum_gib * 1024 * 1024)) + if [ "$available_kib" -lt "$minimum_kib" ]; then + echo "$phase requires at least ${minimum_gib} GiB free on the Docker filesystem" >&2 + return 1 + fi +} + +# This builder belonged only to the retired gateway image-building runner. Cap +# its unused cache before adding the nested daemon; never prune global images, +# volumes, or caches owned by another service on this shared production host. +if docker buildx version >/dev/null 2>&1 && \ + docker buildx inspect "$LEGACY_BUILDER" >/dev/null 2>&1; then + docker buildx prune --builder "$LEGACY_BUILDER" --force --max-used-space 512mb +fi +require_free_space "$MIN_FREE_GIB" "CI runner installation" + +download_verified() { + url=$1 + output=$2 + checksum=$3 + if [ ! -f "$output" ] || ! printf '%s %s\n' "$checksum" "$output" | sha256sum -c - >/dev/null 2>&1; then + rm -f "$output" + curl -fL --retry 5 --retry-delay 2 "$url" -o "$output" + fi + printf '%s %s\n' "$checksum" "$output" | sha256sum -c - +} + +install -d -m 0755 "$PREFIX/bin" "$PREFIX/downloads" "$PREFIX/toolchains" + +download_verified \ + "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" \ + "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" \ + "$SHELLCHECK_SHA256" +rm -rf "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}" +tar -xJf "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" \ + -C "$PREFIX/downloads" +install -m 0755 "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}/shellcheck" "$PREFIX/bin/shellcheck" + +download_verified \ + "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-x86_64" \ + "$PREFIX/downloads/docker-compose-${COMPOSE_VERSION}-linux-x86_64" \ + "$COMPOSE_SHA256" +install -m 0755 "$PREFIX/downloads/docker-compose-${COMPOSE_VERSION}-linux-x86_64" "$PREFIX/bin/docker-compose" + +download_verified \ + "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" \ + "$PREFIX/downloads/go${GO_VERSION}.linux-amd64.tar.gz" \ + "$GO_SHA256" +rm -rf "$PREFIX/toolchains/go" +tar -xzf "$PREFIX/downloads/go${GO_VERSION}.linux-amd64.tar.gz" -C "$PREFIX/toolchains" + +download_verified \ + "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + "$PREFIX/downloads/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + "$NODE_SHA256" +rm -rf "$PREFIX/toolchains/node" +mkdir -p "$PREFIX/toolchains/node" +tar -xJf "$PREFIX/downloads/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + -C "$PREFIX/toolchains/node" --strip-components=1 +PATH="$PREFIX/toolchains/node/bin:$PATH" \ + "$PREFIX/toolchains/node/bin/npm" install --global --prefix "$PREFIX/toolchains/node" \ + --ignore-scripts "pnpm@${PNPM_VERSION}" + +download_verified \ + "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \ + "$PREFIX/downloads/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \ + "$TRIVY_SHA256" +tar -xzf "$PREFIX/downloads/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" -C "$PREFIX/bin" trivy +chmod 0755 "$PREFIX/bin/trivy" +GOBIN="$PREFIX/bin" GOPROXY="${GO_MODULE_PROXY:-https://goproxy.cn,direct}" \ + "$PREFIX/toolchains/go/bin/go" install golang.org/x/vuln/cmd/govulncheck@v1.6.0 +chown -R root:root "$PREFIX" +chmod -R go-w "$PREFIX" + +require_free_space "$MIN_FREE_GIB" "runner image pull" +docker pull "$RUNNER_IMAGE_SOURCE" +runner_runtime_image=$(docker image inspect --format '{{.Id}}' "$RUNNER_IMAGE_SOURCE") +printf '%s\n' "$runner_runtime_image" | grep -Eq '^sha256:[0-9a-f]{64}$' || \ + fail "pinned runner image did not resolve to an immutable local image ID" +require_free_space "$MIN_POST_INSTALL_FREE_GIB" "completed runner image pull" +docker volume create "$RUNNER_VOLUME" >/dev/null + +# The registered runner state and the nested rootless daemon cache share one +# dedicated volume. Persisting the pinned job image lets validation finish +# before registration and avoids racing the first queued workflow for layers. +docker run --rm --pull=never --user 0:0 \ + --volume "$RUNNER_VOLUME:/data" \ + --entrypoint /bin/sh "$runner_runtime_image" \ + -ec 'chown 1000:1000 /data && chmod 0700 /data' + +install -d -m 0755 /etc/easyai-gateway-ci-v2 +if [ -n "$NESTED_DOCKER_REGISTRY_MIRROR" ]; then + printf '{"data-root":"/data/docker","registry-mirrors":["%s"]}\n' "$NESTED_DOCKER_REGISTRY_MIRROR" \ + > /etc/easyai-gateway-ci-v2/daemon.json +else + printf '{"data-root":"/data/docker"}\n' > /etc/easyai-gateway-ci-v2/daemon.json +fi +chmod 0644 /etc/easyai-gateway-ci-v2/daemon.json + +# Docker's official rootless DinD baseline is --privileged. Gitea's example +# additionally names an AppArmor profile, but Docker does not create that host +# profile. Enable it only when this daemon proves it can start the pinned image +# with the profile; otherwise leave the option empty. +RUNNER_SECURITY_OPTIONS= +if docker run --rm --pull=never --privileged \ + --security-opt apparmor=rootlesskit \ + --entrypoint /bin/true "$runner_runtime_image" >/dev/null 2>&1; then + RUNNER_SECURITY_OPTIONS="--security-opt=apparmor=rootlesskit" +elif [ -r /proc/sys/kernel/apparmor_restrict_unprivileged_userns ] && \ + [ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" = "1" ]; then + fail "restricted AppArmor user namespaces require the rootlesskit profile (install it with the host Docker packages)" +fi + +# Exercise the actual daemon startup before registration. This probe has no +# token, state volume, host bind mount, or socket mount. +PROBE_CONTAINER="easyai-gateway-ci-v2-probe-$$" +cleanup_probe() { + docker rm --force "$PROBE_CONTAINER" >/dev/null 2>&1 || true +} +trap 'cleanup_probe' EXIT +trap 'cleanup_probe; exit 129' HUP +trap 'cleanup_probe; exit 130' INT +trap 'cleanup_probe; exit 143' TERM +if [ -n "$RUNNER_SECURITY_OPTIONS" ]; then + set -- "$RUNNER_SECURITY_OPTIONS" +else + set -- +fi +docker run --detach --rm --pull=never \ + --name "$PROBE_CONTAINER" --privileged \ + --cpus "$RUNNER_CPUS" --memory "$RUNNER_MEMORY" \ + --memory-swap "$RUNNER_MEMORY_SWAP" --pids-limit "$RUNNER_PIDS_LIMIT" "$@" \ + --volume "$RUNNER_VOLUME:/data" \ + --volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro \ + --volume "$PREFIX:$PREFIX:ro" \ + --env DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns \ + --env DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520 \ + --env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns \ + --entrypoint dockerd-entrypoint.sh "$runner_runtime_image" >/dev/null +attempt=0 +until docker exec "$PROBE_CONTAINER" docker info >/dev/null 2>&1; do + attempt=$((attempt + 1)) + if [ "$attempt" -ge 30 ] || \ + [ "$(docker inspect --format '{{.State.Running}}' "$PROBE_CONTAINER" 2>/dev/null || true)" != "true" ]; then + docker logs "$PROBE_CONTAINER" 2>&1 || true + fail "pinned rootless Docker-in-Docker probe did not become ready" + fi + sleep 2 +done +[ "$(docker exec "$PROBE_CONTAINER" id -u)" = "1000" ] || \ + fail "rootless Docker-in-Docker probe is not uid 1000" +[ "$(docker inspect --format '{{.HostConfig.Memory}}:{{.HostConfig.MemorySwap}}:{{.HostConfig.NanoCpus}}:{{.HostConfig.PidsLimit}}' "$PROBE_CONTAINER")" = \ + '2147483648:3221225472:2000000000:1024' ] || \ + fail "rootless Docker-in-Docker probe does not have the required aggregate resource limits" +docker exec "$PROBE_CONTAINER" docker info --format '{{range .SecurityOptions}}{{println .}}{{end}}' | \ + grep -Fq 'name=rootless' || fail "rootless Docker-in-Docker probe is not rootless" +[ "$(docker exec "$PROBE_CONTAINER" docker info --format '{{.DockerRootDir}}')" = /data/docker ] || \ + fail "rootless Docker-in-Docker probe is not using its persistent dedicated data root" + +# Complete every slow and capability-sensitive validation before registration; +# otherwise a queued workflow can race the installer for the same image layers. +docker exec "$PROBE_CONTAINER" docker pull "$JOB_IMAGE" +outer_sentinel_pid=$(docker exec "$PROBE_CONTAINER" /bin/sh -c \ + 'sleep 300 >/dev/null 2>&1 & printf "%s\n" "$!"') +case $outer_sentinel_pid in + '' | *[!0-9]*) fail "could not create the outer PID namespace sentinel" ;; +esac +if ! docker exec "$PROBE_CONTAINER" docker run --rm --pull=never \ + --pid=host --entrypoint /bin/sh "$JOB_IMAGE" \ + -ec 'test ! -e "/proc/$1"' sh "$outer_sentinel_pid"; then + docker exec "$PROBE_CONTAINER" kill "$outer_sentinel_pid" >/dev/null 2>&1 || true + fail "nested --pid=host can see the outer runner PID namespace" +fi +docker exec "$PROBE_CONTAINER" kill "$outer_sentinel_pid" >/dev/null 2>&1 || true +docker exec "$PROBE_CONTAINER" docker run --rm --pull=never \ + --volume "$PREFIX:$PREFIX:ro" \ + --entrypoint /bin/sh "$JOB_IMAGE" -ec ' + export PATH=/opt/easyai-gateway-ci/bin:/opt/easyai-gateway-ci/toolchains/go/bin:/opt/easyai-gateway-ci/toolchains/node/bin:$PATH + go version + node --version + pnpm --version + docker-compose version + shellcheck --version + trivy --version + govulncheck -version + ' +cleanup_probe +trap - EXIT HUP INT TERM + +install -m 0644 "$ROOT/deploy/ci/act-runner-v2-config.yaml" /etc/easyai-gateway-ci-v2/config.yaml +umask 077 +printf 'RUNNER_SECURITY_OPTIONS="%s"\nRUNNER_IMAGE_REF="%s"\n' \ + "$RUNNER_SECURITY_OPTIONS" "$runner_runtime_image" \ + > /etc/easyai-gateway-ci-v2/runner.env +install -m 0644 "$ROOT/deploy/ci/easyai-gateway-ci-v2-runner.service" \ + /etc/systemd/system/easyai-gateway-ci-v2-runner.service + +runner_is_registered() { + docker run --rm --pull=never \ + --volume "$RUNNER_VOLUME:/data" \ + --entrypoint /bin/sh "$runner_runtime_image" \ + -ec 'test -s /data/.runner' +} + +if ! runner_is_registered; then + [ -n "${RUNNER_REGISTRATION_TOKEN:-}" ] || \ + fail "RUNNER_REGISTRATION_TOKEN is required for first registration" + docker rm --force easyai-gateway-ci-v2-register >/dev/null 2>&1 || true + printf '%s\n%s\n%s\n' "$GITEA_INSTANCE_URL" "$RUNNER_REGISTRATION_TOKEN" "$RUNNER_NAME" | \ + docker run --rm --interactive --pull=never \ + --name easyai-gateway-ci-v2-register \ + --volume "$RUNNER_VOLUME:/data" \ + --volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro \ + --env HOME=/data \ + --entrypoint /usr/local/bin/gitea-runner \ + "$runner_runtime_image" --config /config.yaml register +fi +unset RUNNER_REGISTRATION_TOKEN +docker run --rm --pull=never \ + --volume "$RUNNER_VOLUME:/data" \ + --entrypoint /bin/sh "$runner_runtime_image" \ + -ec 'test -s /data/.runner && chmod 0600 /data/.runner' + +RUNNER_VALIDATED=0 +cleanup_unvalidated_runner() { + if [ "$RUNNER_VALIDATED" -ne 1 ]; then + systemctl disable --now easyai-gateway-ci-v2-runner.service >/dev/null 2>&1 || true + docker rm --force "$RUNNER_CONTAINER" >/dev/null 2>&1 || true + fi +} +trap 'cleanup_unvalidated_runner' EXIT +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM +systemctl daemon-reload +systemctl enable --now easyai-gateway-ci-v2-runner.service + +attempt=0 +until docker exec "$RUNNER_CONTAINER" docker info >/dev/null 2>&1; do + attempt=$((attempt + 1)) + if [ "$attempt" -ge 30 ]; then + systemctl --no-pager --full status easyai-gateway-ci-v2-runner.service || true + docker logs "$RUNNER_CONTAINER" 2>&1 || true + fail "rootless Docker daemon did not become ready" + fi + sleep 2 +done + +attempt=0 +until docker logs "$RUNNER_CONTAINER" 2>&1 | grep -Fq 'declare successfully'; do + attempt=$((attempt + 1)) + if [ "$attempt" -ge 30 ] || \ + [ "$(docker inspect --format '{{.State.Running}}' "$RUNNER_CONTAINER" 2>/dev/null || true)" != "true" ]; then + docker logs "$RUNNER_CONTAINER" 2>&1 || true + fail "Gitea runner did not declare itself successfully" + fi + sleep 2 +done + +[ "$(docker inspect --format '{{.Config.User}}' "$RUNNER_CONTAINER")" = "rootless" ] || \ + fail "outer runner container is not using the rootless image user" +[ "$(docker inspect --format '{{.HostConfig.Privileged}}' "$RUNNER_CONTAINER")" = "true" ] || \ + fail "outer rootless DinD container is not privileged" +[ "$(docker inspect --format '{{.HostConfig.Memory}}:{{.HostConfig.MemorySwap}}:{{.HostConfig.NanoCpus}}:{{.HostConfig.PidsLimit}}' "$RUNNER_CONTAINER")" = \ + '2147483648:3221225472:2000000000:1024' ] || \ + fail "outer rootless DinD container does not have the required aggregate resource limits" +if docker inspect --format '{{range .Mounts}}{{println .Source}}{{end}}' "$RUNNER_CONTAINER" | \ + grep -Eq '/var/run/docker\.sock|/run/docker\.sock'; then + fail "host Docker socket was mounted into the runner" +fi +docker exec "$RUNNER_CONTAINER" docker info --format '{{range .SecurityOptions}}{{println .}}{{end}}' | \ + grep -Fq 'name=rootless' || fail "nested Docker daemon is not rootless" +[ "$(docker exec "$RUNNER_CONTAINER" docker info --format '{{.DockerRootDir}}')" = /data/docker ] || \ + fail "nested Docker daemon is not using its persistent dedicated data root" +docker exec "$RUNNER_CONTAINER" docker image inspect "$JOB_IMAGE" >/dev/null || \ + fail "validated pinned job image is unavailable after runner activation" +if ! require_free_space "$MIN_POST_INSTALL_FREE_GIB" "completed CI runner installation"; then + fail "CI runner was stopped because the post-install disk reserve was not met" +fi +systemctl --no-pager --full status easyai-gateway-ci-v2-runner.service +RUNNER_VALIDATED=1 +trap - EXIT HUP INT TERM diff --git a/tests/ci/ci-build-images-test.sh b/tests/ci/ci-build-images-test.sh new file mode 100755 index 0000000..bb12662 --- /dev/null +++ b/tests/ci/ci-build-images-test.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +script=$root/scripts/ci-build-images.sh +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +mkdir -p "$tmp/bin" +cat >"$tmp/bin/docker" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +printf 'docker %s\n' "$*" >>"$MOCK_LOG" + +case "$*" in + 'buildx inspect '*|'buildx create '*) exit 0 ;; + 'buildx build '*) + if [[ ${MOCK_BUILD_FAIL:-0} == 1 && $* == *'--target web'* ]]; then + exit 21 + fi + ;; + 'buildx prune '*) + [[ ${MOCK_PRUNE_FAIL:-0} == 0 ]] || exit 22 + ;; + 'image rm '*) + [[ ${MOCK_IMAGE_RM_FAIL:-0} == 0 ]] || exit 23 + ;; + *) + printf 'unexpected docker invocation: %s\n' "$*" >&2 + exit 99 + ;; +esac +MOCK +cat >"$tmp/bin/trivy" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +printf 'trivy %s\n' "$*" >>"$MOCK_LOG" +MOCK +chmod +x "$tmp/bin/docker" "$tmp/bin/trivy" + +sha=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +registry=registry.example.com/easyai +api_image=$registry/ai-gateway:$sha +web_image=$registry/ai-gateway-web:$sha + +run_build() { + env \ + PATH="$tmp/bin:$PATH" \ + MOCK_LOG="$tmp/calls.log" \ + IMAGE_TAG="$sha" \ + AI_GATEWAY_IMAGE_REGISTRY="$registry" \ + AI_GATEWAY_BUILDX_BUILDER=gateway-test \ + AI_GATEWAY_BUILDX_CACHE_LIMIT=2gb \ + "$script" "$@" +} + +: >"$tmp/calls.log" +run_build +grep -Fq "docker buildx prune --builder gateway-test --force --max-used-space 2gb" "$tmp/calls.log" +grep -Fq "docker image rm $api_image $web_image" "$tmp/calls.log" +grep -Fq 'trivy image --db-repository ghcr.m.daocloud.io/aquasecurity/trivy-db:2' \ + "$tmp/calls.log" + +: >"$tmp/calls.log" +run_build --keep-images +grep -Fq "docker buildx prune --builder gateway-test --force --max-used-space 2gb" "$tmp/calls.log" +if grep -Fq 'docker image rm ' "$tmp/calls.log"; then + echo '--keep-images must preserve fully built images' >&2 + exit 1 +fi + +: >"$tmp/calls.log" +if MOCK_PRUNE_FAIL=1 run_build; then + echo 'a prune failure must fail an otherwise successful build' >&2 + exit 1 +fi + +: >"$tmp/calls.log" +if MOCK_IMAGE_RM_FAIL=1 run_build; then + echo 'an image cleanup failure must fail an otherwise successful build' >&2 + exit 1 +fi + +: >"$tmp/calls.log" +if MOCK_BUILD_FAIL=1 run_build; then + echo 'a failed image build must fail the script' >&2 + exit 1 +fi +grep -Fq "docker image rm $api_image $web_image" "$tmp/calls.log" + +for invalid_limit in 0gb 2 2GB -1gb '2gb --all'; do + : >"$tmp/calls.log" + if env \ + PATH="$tmp/bin:$PATH" \ + MOCK_LOG="$tmp/calls.log" \ + IMAGE_TAG="$sha" \ + AI_GATEWAY_IMAGE_REGISTRY="$registry" \ + AI_GATEWAY_BUILDX_CACHE_LIMIT="$invalid_limit" \ + "$script" >/dev/null 2>&1; then + printf 'invalid cache limit was accepted: %s\n' "$invalid_limit" >&2 + exit 1 + fi + if [[ -s $tmp/calls.log ]]; then + printf 'invalid cache limit reached Docker: %s\n' "$invalid_limit" >&2 + exit 1 + fi +done + +echo 'ci_build_images_tests=PASS' diff --git a/tests/ci/migrations-test.sh b/tests/ci/migrations-test.sh new file mode 100755 index 0000000..816ce25 --- /dev/null +++ b/tests/ci/migrations-test.sh @@ -0,0 +1,331 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +validator=$root/scripts/ci-validate-migrations.mjs +fixture_root=$(mktemp -d) +trap 'rm -rf "$fixture_root"' EXIT + +new_repo() { + local name=$1 + local repo=$fixture_root/$name + + mkdir -p "$repo/apps/api/migrations" + git -C "$repo" init -q + git -C "$repo" config user.name 'CI Migration Test' + git -C "$repo" config user.email 'ci-migration-test@example.invalid' + printf 'CREATE TABLE accounts (id bigint PRIMARY KEY);\n' > \ + "$repo/apps/api/migrations/0001_init.sql" + git -C "$repo" add apps/api/migrations/0001_init.sql + git -C "$repo" commit -qm baseline + mkdir -p "$repo/deploy/ci" + git -C "$repo" rev-parse HEAD > \ + "$repo/deploy/ci/production-migration-base" + printf '%s\n' "$repo" +} + +commit_all() { + local repo=$1 + local message=$2 + git -C "$repo" add -A + git -C "$repo" commit -qm "$message" +} + +expect_pass() { + local repo=$1 + local base=$2 + local immutable_base=${3:-} + ( + cd "$repo" + if [[ -n $immutable_base ]]; then + node "$validator" "$base" "$immutable_base" + else + node "$validator" "$base" + fi + ) +} + +expect_fail() { + local repo=$1 + local base=$2 + local expected=$3 + local immutable_base=${4:-} + local output status + + if [[ -n $immutable_base ]]; then + set +e + output=$(cd "$repo" && node "$validator" "$base" "$immutable_base" 2>&1) + status=$? + set -e + else + set +e + output=$(cd "$repo" && node "$validator" "$base" 2>&1) + status=$? + set -e + fi + if [[ $status -eq 0 ]]; then + printf 'migration validator unexpectedly passed: %s\n' "$expected" >&2 + exit 1 + fi + grep -Fq "$expected" <<<"$output" || { + printf 'missing expected migration failure %q in:\n%s\n' "$expected" "$output" >&2 + exit 1 + } +} + +repo=$(new_repo safe) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_expand_accounts.sql" <<'SQL' +-- A comment mentioning DROP TABLE must not trigger the gate. +ALTER TABLE accounts ADD COLUMN display_name text; +CREATE TABLE audit_events (id bigint PRIMARY KEY, note text); +INSERT INTO audit_events (id, note) VALUES (1, 'DROP TABLE is documentation'); +UPDATE accounts SET display_name = 'ready' WHERE display_name IS NULL; +SQL +commit_all "$repo" safe +expect_pass "$repo" "$base" + +repo=$(new_repo drop) +base=$(git -C "$repo" rev-parse HEAD) +printf 'DROP\nTABLE accounts;\n' >"$repo/apps/api/migrations/0002_drop.sql" +commit_all "$repo" drop +expect_fail "$repo" "$base" 'destructive DROP operation' + +repo=$(new_repo delete) +base=$(git -C "$repo" rev-parse HEAD) +printf 'DELETE FROM accounts;\n' >"$repo/apps/api/migrations/0002_delete.sql" +commit_all "$repo" delete +expect_fail "$repo" "$base" 'DELETE FROM operation' + +repo=$(new_repo incompatible_alter) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_alter.sql" <<'SQL' +ALTER TABLE accounts ADD COLUMN external_id text NOT NULL; +ALTER TABLE accounts ALTER COLUMN id TYPE numeric; +SQL +commit_all "$repo" alter +expect_fail "$repo" "$base" 'non-null column addition' +expect_fail "$repo" "$base" 'column type change' + +repo=$(new_repo dynamic_sql) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_dynamic.sql" <<'SQL' +DO $$ +BEGIN + EXECUTE 'DROP ' || 'TABLE accounts'; +END +$$; +SQL +commit_all "$repo" dynamic +expect_fail "$repo" "$base" 'dynamic SQL execution' + +repo=$(new_repo escape_string) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_escape.sql" <<'SQL' +SELECT E'foo\'bar'; DROP TABLE accounts; --'; +SQL +commit_all "$repo" escape +expect_fail "$repo" "$base" 'PostgreSQL escape/unicode strings are not allowed' + +repo=$(new_repo string_semantics_override) +base=$(git -C "$repo" rev-parse HEAD) +printf 'SET standard_conforming_strings = off;\n' > \ + "$repo/apps/api/migrations/0002_string_mode.sql" +commit_all "$repo" string_mode +expect_fail "$repo" "$base" 'SQL string semantics override' + +repo=$(new_repo transaction_control) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_commit.sql" <<'SQL' +ALTER TABLE accounts ADD COLUMN display_name text; +COMMIT; +UPDATE accounts SET display_name = 'partially committed'; +SQL +commit_all "$repo" transaction_control +expect_fail "$repo" "$base" 'transaction control operation' + +repo=$(new_repo bare_end_transaction) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_end.sql" <<'SQL' +ALTER TABLE accounts ADD COLUMN display_name text; +END; +UPDATE accounts SET display_name = 'partially committed'; +SQL +commit_all "$repo" bare_end_transaction +expect_fail "$repo" "$base" 'transaction control operation' + +repo=$(new_repo plpgsql_block_end) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_function.sql" <<'SQL' +CREATE FUNCTION account_count() RETURNS bigint +LANGUAGE plpgsql AS $$ +BEGIN + RETURN (SELECT count(*) FROM accounts); +END; +$$; +SQL +commit_all "$repo" plpgsql_block_end +expect_fail "$repo" "$base" 'procedural SQL body' + +repo=$(new_repo ordinary_string_procedural_body) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_do_string.sql" <<'SQL' +DO LANGUAGE plpgsql 'BEGIN DROP TABLE accounts; END'; +SQL +commit_all "$repo" ordinary_string_procedural_body +expect_fail "$repo" "$base" 'procedural SQL body' + +repo=$(new_repo ordinary_string_boundary) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_string_boundary.sql" <<'SQL' +SELECT '\'; DROP TABLE accounts; --'; +SQL +commit_all "$repo" ordinary_string +expect_fail "$repo" "$base" 'destructive DROP operation' + +repo=$(new_repo unicode_string) +base=$(git -C "$repo" rev-parse HEAD) +printf "SELECT U&'d\\0061ta';\n" >"$repo/apps/api/migrations/0002_unicode.sql" +commit_all "$repo" unicode +expect_fail "$repo" "$base" 'PostgreSQL escape/unicode strings are not allowed' + +repo=$(new_repo merge_delete) +base=$(git -C "$repo" rev-parse HEAD) +cat >"$repo/apps/api/migrations/0002_merge.sql" <<'SQL' +MERGE INTO accounts USING stale_accounts ON accounts.id = stale_accounts.id +WHEN MATCHED THEN DELETE; +SQL +commit_all "$repo" merge +expect_fail "$repo" "$base" 'MERGE operation' + +repo=$(new_repo replace_view) +base=$(git -C "$repo" rev-parse HEAD) +printf 'CREATE OR REPLACE VIEW active_accounts AS SELECT id FROM accounts;\n' > \ + "$repo/apps/api/migrations/0002_replace.sql" +commit_all "$repo" replace +expect_fail "$repo" "$base" 'CREATE OR REPLACE operation' + +repo=$(new_repo rename_function) +base=$(git -C "$repo" rev-parse HEAD) +printf 'ALTER FUNCTION calculate_total() RENAME TO calculate_total_legacy;\n' > \ + "$repo/apps/api/migrations/0002_rename.sql" +commit_all "$repo" rename +expect_fail "$repo" "$base" 'object or column rename' + +repo=$(new_repo set_schema) +base=$(git -C "$repo" rev-parse HEAD) +printf 'ALTER TABLE accounts SET SCHEMA archive;\n' > \ + "$repo/apps/api/migrations/0002_schema.sql" +commit_all "$repo" schema +expect_fail "$repo" "$base" 'SET SCHEMA operation' + +repo=$(new_repo drop_domain) +base=$(git -C "$repo" rev-parse HEAD) +printf 'DROP DOMAIN account_code CASCADE;\n' > \ + "$repo/apps/api/migrations/0002_drop_domain.sql" +commit_all "$repo" drop_domain +expect_fail "$repo" "$base" 'destructive DROP operation' + +repo=$(new_repo immutable) +base=$(git -C "$repo" rev-parse HEAD) +printf '\n-- retroactive edit\n' >>"$repo/apps/api/migrations/0001_init.sql" +commit_all "$repo" edit +expect_fail "$repo" "$base" 'applied migrations are immutable (status M)' + +repo=$(new_repo immutable_after_baseline) +base=$(git -C "$repo" rev-parse HEAD) +printf 'ALTER TABLE accounts ADD COLUMN nickname text;\n' > \ + "$repo/apps/api/migrations/0002_nickname.sql" +commit_all "$repo" add +immutable_base=$(git -C "$repo" rev-parse HEAD) +printf '\nUPDATE accounts SET nickname = NULL;\n' >> \ + "$repo/apps/api/migrations/0002_nickname.sql" +commit_all "$repo" modify +output=$( + cd "$repo" + node "$validator" "$base" "$immutable_base" 2>&1 || true +) +grep -Fq 'migration present on main is immutable (status M)' <<<"$output" || { + printf 'stale production baseline did not preserve main migration immutability:\n%s\n' \ + "$output" >&2 + exit 1 +} + +repo=$(new_repo filename) +base=$(git -C "$repo" rev-parse HEAD) +printf 'SELECT 1;\n' >"$repo/apps/api/migrations/next.SQL" +commit_all "$repo" filename +expect_fail "$repo" "$base" 'new migration must use NNNN_lowercase_name.sql' + +repo=$(new_repo ordering) +base=$(git -C "$repo" rev-parse HEAD) +printf 'SELECT 1;\n' >"$repo/apps/api/migrations/0000_late.sql" +commit_all "$repo" ordering +expect_fail "$repo" "$base" 'new migration name must sort after 0001_init.sql' + +repo=$(new_repo stale_baseline) +base=$(git -C "$repo" rev-parse HEAD) +printf 'ALTER TABLE accounts ADD COLUMN nickname text;\n' > \ + "$repo/apps/api/migrations/0002_nickname.sql" +commit_all "$repo" main_migration +immutable_base=$(git -C "$repo" rev-parse HEAD) +printf 'ordinary code change\n' >"$repo/README.md" +commit_all "$repo" code_only +expect_pass "$repo" "$base" "$immutable_base" + +repo=$(new_repo baseline_advance) +production_base=$(git -C "$repo" rev-parse HEAD) +mkdir -p "$repo/deploy/ci" +printf '%s\n' "$production_base" >"$repo/deploy/ci/production-migration-base" +commit_all "$repo" baseline_file +immutable_base=$(git -C "$repo" rev-parse HEAD) +git -C "$repo" switch -q -c release-baseline-update "$immutable_base" +printf '%s\n' "$immutable_base" >"$repo/deploy/ci/production-migration-base" +commit_all "$repo" valid_baseline_advance +expect_pass "$repo" "$immutable_base" "$immutable_base" + +git -C "$repo" switch -q -c malicious-baseline "$immutable_base" +printf 'DROP TABLE accounts;\n' >"$repo/apps/api/migrations/0002_drop.sql" +commit_all "$repo" hidden_destructive_migration +hidden_sha=$(git -C "$repo" rev-parse HEAD) +printf '%s\n' "$hidden_sha" >"$repo/deploy/ci/production-migration-base" +commit_all "$repo" hide_migration_in_baseline +expect_fail "$repo" "$hidden_sha" \ + 'production migration baseline must belong to the immutable event base history' \ + "$immutable_base" + +repo=$(new_repo nested) +base=$(git -C "$repo" rev-parse HEAD) +mkdir -p "$repo/apps/api/migrations/archive" +printf 'SELECT 1;\n' >"$repo/apps/api/migrations/archive/9999_hidden.sql" +commit_all "$repo" nested +expect_fail "$repo" "$base" 'new migration must use NNNN_lowercase_name.sql' + +repo=$(new_repo symlink) +base=$(git -C "$repo" rev-parse HEAD) +printf 'SELECT 1;\n' >"$repo/outside.sql" +ln -s ../../../outside.sql "$repo/apps/api/migrations/0002_symlink.sql" +commit_all "$repo" symlink +expect_fail "$repo" "$base" 'new migration must be a mode 100644 regular file' + +repo=$(new_repo baseline_symlink) +base=$(git -C "$repo" rev-parse HEAD) +printf '%s\n' "$base" >"$repo/baseline-target" +rm "$repo/deploy/ci/production-migration-base" +ln -s ../../baseline-target "$repo/deploy/ci/production-migration-base" +commit_all "$repo" baseline_symlink +expect_fail "$repo" "$base" \ + 'production migration baseline must be a mode 100644 regular file' + +repo=$(new_repo ancestry) +base=$(git -C "$repo" rev-parse HEAD) +git -C "$repo" switch -q --orphan unrelated +printf 'unrelated\n' >"$repo/unrelated" +git -C "$repo" add unrelated +git -C "$repo" commit -qm unrelated +expect_fail "$repo" "$base" 'production migration baseline is not an ancestor of HEAD' + +repo=$(new_repo invalid_sha) +expect_fail "$repo" deadbeef 'must be a full lowercase commit SHA' + +echo 'migration_tests=PASS' diff --git a/tests/ci/pipeline-test.sh b/tests/ci/pipeline-test.sh new file mode 100755 index 0000000..ef4b707 --- /dev/null +++ b/tests/ci/pipeline-test.sh @@ -0,0 +1,323 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 # Assertions intentionally match literal workflow variables. +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +workflow=$root/.gitea/workflows/ci.yml +release_workflow=$root/.gitea/workflows/release-ci.yml +runner_service=$root/deploy/ci/easyai-gateway-ci-v2-runner.service +runner_config=$root/deploy/ci/act-runner-v2-config.yaml +provision=$root/scripts/provision-ci-runner.sh +build_images=$root/scripts/ci-build-images.sh +validate_semver=$root/scripts/ci-validate-semver.sh +validate_migrations=$root/scripts/ci-validate-migrations.mjs +build_images_test=$root/tests/ci/ci-build-images-test.sh +migrations_test=$root/tests/ci/migrations-test.sh +migration_base=$root/deploy/ci/production-migration-base +adr=$root/docs/decisions/001-production-cicd.md +runbook=$root/docs/runbooks/production-ci-cd.md +migrator=$root/apps/api/cmd/migrate/main.go + +for file in "$workflow" "$release_workflow" "$runner_service" "$runner_config" "$provision" "$build_images" "$validate_semver" "$validate_migrations" "$build_images_test" "$migrations_test" "$migration_base" "$adr" "$runbook" "$migrator"; do + [[ -f $file ]] || { + printf 'missing CI/CD file: %s\n' "$file" >&2 + exit 1 + } +done + +if [[ $(wc -l <"$migration_base") -ne 1 ]]; then + echo 'production migration baseline must contain exactly one line' >&2 + exit 1 +fi +grep -Fq 'SET standard_conforming_strings = on' "$migrator" || { + echo 'database migrator does not pin standard SQL string semantics' >&2 + exit 1 +} +if ! grep -Eq '^[0-9a-f]{40}$' "$migration_base"; then + echo 'production migration baseline must be a full lowercase SHA' >&2 + exit 1 +fi + +grep -q '^name: ci$' "$workflow" +grep -q '^ push:$' "$workflow" +grep -q '^ branches: \[main\]$' "$workflow" +grep -q '^ pull_request:$' "$workflow" +grep -q '^ verify:$' "$workflow" +grep -Fq 'git checkout --detach "$CI_SHA"' "$workflow" +grep -Fq 'test "$(git rev-parse HEAD)" = "$CI_SHA"' "$workflow" +if grep -Fq 'git checkout --detach FETCH_HEAD' "$workflow"; then + echo 'main/PR CI must not checkout mutable FETCH_HEAD after comparison fetches' >&2 + exit 1 +fi +if grep -Eq "^[[:space:]]+tags:|Verify release tag ancestry" "$workflow" || \ + grep -Fq './scripts/ci-validate-semver.sh "$tag_name"' "$workflow"; then + echo 'main/PR CI must not share the release-tag context' >&2 + exit 1 +fi + +grep -q '^name: release-ci$' "$release_workflow" +grep -q '^ push:$' "$release_workflow" +grep -q "^ tags: \['v\*'\]$" "$release_workflow" +grep -q '^ verify-tag:$' "$release_workflow" +grep -q '^ - name: Verify release tag ancestry$' "$release_workflow" +grep -Fq './scripts/ci-validate-semver.sh "$tag_name"' "$release_workflow" +grep -Fq 'git merge-base --is-ancestor "$CI_SHA" refs/remotes/origin/main' "$release_workflow" +if grep -Eq 'branches:|pull_request:' "$release_workflow"; then + echo 'release CI must be a tag-only workflow/context' >&2 + exit 1 +fi + +for quality_workflow in "$workflow" "$release_workflow"; do + grep -q '^ runs-on: easyai-gateway-ci-unprivileged-v2$' "$quality_workflow" + grep -q '^ - name: Checkout without external Actions$' "$quality_workflow" + if grep -q -- '--depth=' "$quality_workflow"; then + echo 'quality verification must not use a shallow checkout' >&2 + exit 1 + fi + grep -Fq 'test ! -f .git/shallow' "$quality_workflow" + grep -Fq './tests/ci/ci-build-images-test.sh' "$quality_workflow" + grep -Fq './tests/ci/migrations-test.sh' "$quality_workflow" + grep -Fq './tests/ci/pipeline-test.sh' "$quality_workflow" + grep -Fq './tests/ci/semver-test.sh' "$quality_workflow" + grep -Fq 'docker-compose version' "$quality_workflow" + grep -Fq 'docker-compose -f docker-compose.yml config --quiet' "$quality_workflow" + if grep -Fq 'docker compose' "$quality_workflow"; then + echo 'job container must use the read-only standalone Compose binary' >&2 + exit 1 + fi + + for forbidden in \ + 'sudo' \ + '/usr/local/sbin/easyai-ai-gateway-release' \ + 'docker version' \ + 'docker buildx' \ + './scripts/ci-build-images.sh --keep-images' \ + 'Deploy verified release to Production'; do + if grep -Fq "$forbidden" "$quality_workflow"; then + printf 'unprivileged workflow contains forbidden capability: %s\n' "$forbidden" >&2 + exit 1 + fi + done + + if grep -Eq '^[[:space:]]+\./scripts/ci-build-images\.sh([[:space:]]|$)' "$quality_workflow"; then + echo 'unprivileged workflow must not build container images' >&2 + exit 1 + fi + + for gate in \ + 'scripts/ci-validate-migrations.mjs' \ + 'gofmt -l' \ + 'go vet ./...' \ + 'go test ./...' \ + 'pnpm install --frozen-lockfile' \ + 'pnpm lint' \ + 'pnpm test' \ + 'pnpm build' \ + 'pnpm audit --audit-level high' \ + 'docker-compose -f docker-compose.yml config --quiet' \ + 'govulncheck ./...' \ + 'trivy fs'; do + grep -Fq "$gate" "$quality_workflow" || { + printf '%s is missing quality gate: %s\n' "$quality_workflow" "$gate" >&2 + exit 1 + } + done + grep -Fq 'TRIVY_DB_REPOSITORY: ghcr.m.daocloud.io/aquasecurity/trivy-db:2' \ + "$quality_workflow" || { + printf '%s does not pin the reachable Trivy vulnerability DB mirror\n' \ + "$quality_workflow" >&2 + exit 1 + } + grep -Fq 'production_base=$(cat deploy/ci/production-migration-base)' \ + "$quality_workflow" || { + printf '%s does not read the versioned production migration baseline\n' \ + "$quality_workflow" >&2 + exit 1 + } +done + +if [[ $(sed -n '/^ - name: Verify Go formatting$/,$p' "$workflow") != \ + "$(sed -n '/^ - name: Verify Go formatting$/,$p' "$release_workflow")" ]]; then + echo 'main/PR and release-tag workflows must have identical quality gates' >&2 + exit 1 +fi + +grep -Fq 'CI_EVENT_BEFORE: ${{ github.event.before }}' "$workflow" +grep -Fq 'CI_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}' "$workflow" +grep -Fq 'git merge-base --is-ancestor "$immutable_base" HEAD' "$workflow" +grep -Fq '"$production_base" "$immutable_base"' "$workflow" +grep -Fq 'node ./scripts/ci-validate-migrations.mjs "$production_base"' \ + "$release_workflow" + +runner_image='docker.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a' +job_image='docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7' + +grep -q '^User=root$' "$runner_service" +grep -q '^Group=root$' "$runner_service" +grep -q '^NoNewPrivileges=true$' "$runner_service" +grep -q '^ProtectSystem=strict$' "$runner_service" +grep -q '^ProtectHome=true$' "$runner_service" +grep -q '^CapabilityBoundingSet=$' "$runner_service" +grep -Fq 'Requires=docker.service' "$runner_service" +grep -Fq -- "--pull=never" "$runner_service" +grep -Fq -- "--privileged" "$runner_service" +grep -Fq -- '--pids-limit 1024' "$runner_service" +grep -Fq -- '--cpus 2' "$runner_service" +grep -Fq -- '--memory 2g' "$runner_service" +grep -Fq -- '--memory-swap 3g' "$runner_service" +grep -Fq -- '--env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns' "$runner_service" +grep -Fq 'EnvironmentFile=-/etc/easyai-gateway-ci-v2/runner.env' "$runner_service" +grep -Fq '$RUNNER_SECURITY_OPTIONS' "$runner_service" +grep -Fq -- "--volume easyai-gateway-ci-v2-data:/data" "$runner_service" +grep -Fq -- "--volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro" "$runner_service" +grep -Fq -- "--volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro" "$runner_service" +grep -Fq -- "--volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro" "$runner_service" +grep -Fq 'Environment=RUNNER_IMAGE_REF=' "$runner_service" +grep -Fq '$RUNNER_IMAGE_REF' "$runner_service" +if grep -Eq 'GITEA_RUNNER_REGISTRATION_TOKEN|/var/run/docker\.sock|/run/docker\.sock' "$runner_service"; then + echo 'persistent runner service exposes a registration token or host Docker socket' >&2 + exit 1 +fi +if grep -Fq 'apparmor=rootlesskit' "$runner_service"; then + echo 'runner service must not hard-code an AppArmor profile that may be absent' >&2 + exit 1 +fi + +grep -Fq "easyai-gateway-ci-unprivileged-v2:docker://$job_image" "$runner_config" +if grep -Fq ':host' "$runner_config"; then + echo 'CI runner must never use the host executor' >&2 + exit 1 +fi +grep -q '^ capacity: 1$' "$runner_config" +grep -q '^ file: /data/.runner$' "$runner_config" +grep -q '^ docker_host: "-"$' "$runner_config" +grep -q '^ privileged: false$' "$runner_config" +grep -q '^ require_docker: true$' "$runner_config" +grep -q '^ bind_workdir: false$' "$runner_config" +grep -Fq ' options: "--volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro"' "$runner_config" +grep -q '^ valid_volumes:$' "$runner_config" +grep -q '^ - "/opt/easyai-gateway-ci"$' "$runner_config" +if awk ' + /^ valid_volumes:$/ { in_volumes=1; next } + in_volumes && /^ - / { count++; next } + in_volumes && /^ [A-Za-z_]+:/ { in_volumes=0 } + END { exit count == 1 ? 0 : 1 } +' "$runner_config"; then + : +else + echo 'runner must allow exactly one workflow volume source' >&2 + exit 1 +fi +if grep -Eq '/var/run/docker\.sock|/run/docker\.sock' "$runner_config"; then + echo 'job configuration must not expose a Docker socket' >&2 + exit 1 +fi +if grep -Eq 'apparmor=unconfined|sysctl .*(apparmor_restrict_unprivileged_userns)' "$runner_service" "$runner_config" "$provision"; then + echo 'runner must not disable AppArmor isolation or its user-namespace restriction' >&2 + exit 1 +fi + +grep -q '^GITEA_RUNNER_VERSION=2.0.0$' "$provision" +grep -q '^GO_VERSION=1.26.5$' "$provision" +grep -q '^NODE_VERSION=24.16.0$' "$provision" +grep -q '^TRIVY_VERSION=0.70.0$' "$provision" +grep -q '^PREFIX=/opt/easyai-gateway-ci$' "$provision" +grep -q '^RUNNER_VOLUME=easyai-gateway-ci-v2-data$' "$provision" +grep -q '^SHELLCHECK_VERSION=0.11.0$' "$provision" +grep -q '^SHELLCHECK_SHA256=8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198$' "$provision" +grep -q '^COMPOSE_VERSION=5.3.1$' "$provision" +grep -q '^COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959$' "$provision" +grep -Fq 'RUNNER_NAME=${RUNNER_NAME:-easyai-gateway-ci-unprivileged-v2}' "$provision" +grep -Fq "RUNNER_IMAGE='$runner_image'" "$provision" +grep -Fq 'RUNNER_IMAGE_SOURCE=${CI_RUNNER_IMAGE_SOURCE:-$RUNNER_IMAGE}' "$provision" +grep -Fq 'runner_manifest_digest=${RUNNER_IMAGE##*@}' "$provision" +grep -Fq 'CI_RUNNER_IMAGE_SOURCE must use the pinned runner manifest digest' "$provision" +grep -Fq 'runner_runtime_image=$(docker image inspect' "$provision" +grep -Fq 'RUNNER_IMAGE_REF="%s"' "$provision" +grep -Fq "JOB_IMAGE='$job_image'" "$provision" +grep -Fq 'RUNNER_LABEL="easyai-gateway-ci-unprivileged-v2:docker://$JOB_IMAGE"' "$provision" +grep -Fq 'docker volume create "$RUNNER_VOLUME"' "$provision" +grep -Fq '"data-root":"/data/docker"' "$provision" +grep -Fq -- '--security-opt apparmor=rootlesskit' "$provision" +grep -Fq 'RUNNER_SECURITY_OPTIONS="--security-opt=apparmor=rootlesskit"' "$provision" +grep -Fq 'RUNNER_SECURITY_OPTIONS=' "$provision" +grep -Fq '/proc/sys/kernel/apparmor_restrict_unprivileged_userns' "$provision" +grep -Fq -- '--entrypoint dockerd-entrypoint.sh "$runner_runtime_image"' "$provision" +grep -Fq '/etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro' "$provision" +grep -Fq 'CI_NESTED_DOCKER_REGISTRY_MIRROR must be an HTTPS registry origin' "$provision" +grep -Fq 'PROBE_CONTAINER="easyai-gateway-ci-v2-probe-$$"' "$provision" +grep -Fq -- '--pids-limit "$RUNNER_PIDS_LIMIT"' "$provision" +grep -Fq -- '--cpus "$RUNNER_CPUS"' "$provision" +grep -Fq -- '--memory "$RUNNER_MEMORY"' "$provision" +grep -Fq -- '--memory-swap "$RUNNER_MEMORY_SWAP"' "$provision" +grep -Fq "'2147483648:3221225472:2000000000:1024'" "$provision" +grep -Fq -- '--entrypoint /usr/local/bin/gitea-runner' "$provision" +grep -Fq 'shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz' "$provision" +grep -Fq 'docker-compose-linux-x86_64' "$provision" +grep -Fq 'install -m 0755 "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}/shellcheck" "$PREFIX/bin/shellcheck"' "$provision" +grep -Fq 'install -m 0755 "$PREFIX/downloads/docker-compose-${COMPOSE_VERSION}-linux-x86_64" "$PREFIX/bin/docker-compose"' "$provision" +grep -Fq 'PATH="$PREFIX/toolchains/node/bin:$PATH"' "$provision" +grep -Fq 'gpasswd -d easyai-gateway-ci-v2 docker' "$provision" +grep -Fq 'gpasswd -d easyai-gateway-ci-v2 sudo' "$provision" +if grep -Fq 'usermod -aG docker' "$provision"; then + echo 'unprivileged runner must never join the Docker group' >&2 + exit 1 +fi +grep -Fq 'systemctl disable --now easyai-gateway-act-runner.service' "$provision" +grep -Fq 'systemctl disable --now easyai-gateway-ci-v2-runner.service' "$provision" +grep -Fq 'RUNNER_REGISTRATION_TOKEN is required for first registration' "$provision" +grep -Fq 'printf '\''%s\n%s\n%s\n'\'' "$GITEA_INSTANCE_URL" "$RUNNER_REGISTRATION_TOKEN" "$RUNNER_NAME"' "$provision" +if grep -Fq -- '--token "$RUNNER_REGISTRATION_TOKEN"' "$provision"; then + echo 'registration token must not be exposed in a host-visible process argv' >&2 + exit 1 +fi +grep -Fq "grep -Fq 'name=rootless'" "$provision" +grep -Fq "grep -Fq 'declare successfully'" "$provision" +grep -Fq 'docker buildx prune --builder "$LEGACY_BUILDER" --force --max-used-space 512mb' "$provision" +grep -Fq 'require_free_space "$MIN_FREE_GIB" "runner image pull"' "$provision" +grep -Fq 'require_free_space "$MIN_POST_INSTALL_FREE_GIB" "completed CI runner installation"' "$provision" +grep -Fq 'CI_RUNNER_MIN_FREE_GIB cannot be lower than 8' "$provision" +grep -Fq 'CI_RUNNER_MIN_POST_INSTALL_FREE_GIB cannot be lower than 4' "$provision" +grep -Fq 'docker exec "$PROBE_CONTAINER" docker pull "$JOB_IMAGE"' "$provision" +grep -Fq 'docker exec "$PROBE_CONTAINER" docker run --rm --pull=never' "$provision" +grep -Fq -- '--volume "$RUNNER_VOLUME:/data"' "$provision" +grep -Fq -- '--volume "$PREFIX:$PREFIX:ro"' "$provision" +probe_pull_line=$(grep -nF 'docker exec "$PROBE_CONTAINER" docker pull "$JOB_IMAGE"' "$provision" | cut -d: -f1) +registration_line=$(grep -nF 'if ! runner_is_registered; then' "$provision" | cut -d: -f1) +[[ -n $probe_pull_line && -n $registration_line && $probe_pull_line -lt $registration_line ]] || { + echo 'pinned job image and isolation probes must complete before runner registration' >&2 + exit 1 +} +grep -Fq 'nested --pid=host can see the outer runner PID namespace' "$provision" +grep -Fq 'chown -R root:root "$PREFIX"' "$provision" +grep -Fq 'chmod -R go-w "$PREFIX"' "$provision" +if grep -Eq 'SHELLCHECK_BIN|COMPOSE_PLUGIN|command -v shellcheck|docker compose version' "$provision"; then + echo 'runner provisioning must use checksum-pinned static CI tools, not host copies' >&2 + exit 1 +fi +if grep -Eq 'docker (system|builder|image|volume) prune' "$provision"; then + echo 'runner provisioning must not globally prune shared Docker state' >&2 + exit 1 +fi + +grep -Fq 'root-owned dispatcher' "$adr" +grep -Fq 'release-ci / verify-tag (push)' "$adr" +grep -Fq '来自 Fork 的 PR 必须先由维护者检查工作流变更并在 Gitea 中批准' "$adr" +grep -Fq 'rootless DinD 不是 VM 沙箱' "$runbook" +grep -Fq '同时等待 `ci / verify (push)` 与本次新产生的 `release-ci / verify-tag (push)`' "$runbook" +grep -Fq '不得执行 Tag checkout 中的' "$runbook" + +grep -Fq 'docker buildx build --builder "$builder"' "$build_images" +grep -q -- '--target api' "$build_images" +grep -q -- '--target web' "$build_images" +grep -Fq 'docker buildx prune --builder "$builder" --force --max-used-space "$cache_limit"' "$build_images" +grep -Fq 'trivy image' "$build_images" +grep -Fq 'docker image rm "$api_image" "$web_image"' "$build_images" +grep -Fq 'AI_GATEWAY_BUILDX_CACHE_LIMIT has an invalid format' "$build_images" + +if grep -En '(password|secret|token):[[:space:]]+[^$<{]' "$workflow" "$release_workflow"; then + echo 'workflow contains a literal credential' >&2 + exit 1 +fi + +echo 'gateway_ci_pipeline_tests=PASS' diff --git a/tests/ci/semver-test.sh b/tests/ci/semver-test.sh new file mode 100755 index 0000000..fde3ed3 --- /dev/null +++ b/tests/ci/semver-test.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +validator=$root/scripts/ci-validate-semver.sh + +valid_tags=( + v0.0.0 + v1.2.3 + v1.2.3-alpha + v1.2.3-alpha.1 + v1.2.3-0.3.7 + v1.2.3-01a + v1.2.3-alpha-beta + v1.2.3-- + v1.2.3-x.7.z.92+build.5 + v1.2.3+build.11.e0f985a + v1.2.3+001 +) +invalid_tags=( + 1.2.3 + v01.2.3 + v1.02.3 + v1.2.03 + v1.2.3.foo + v1.2.3. + v1.2.3-. + v1.2.3-alpha. + v1.2.3-alpha..1 + v1.2.3-01 + v1.2.3-00.1 + v1.2.3+ + v1.2.3+build. + v1.2.3+build..1 + v1.2.3+build+1 + 'v1.2.3 ' + v1.2.3-alpha_1 + v1.2 +) + +for tag in "${valid_tags[@]}"; do + "$validator" "$tag" +done + +for tag in "${invalid_tags[@]}"; do + if "$validator" "$tag" >/dev/null 2>&1; then + printf 'invalid SemVer tag was accepted: %s\n' "$tag" >&2 + exit 1 + fi +done + +echo 'semver_tests=PASS'