diff --git a/.dockerignore b/.dockerignore index 42710b5f5e..3dd0aeaabc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -29,5 +29,9 @@ **/values.dev.yaml **/build **/dist +**/.github +**/.turbo +**/.infra + LICENSE README.md diff --git a/.env.docker-compose.dev b/.env.docker-compose.dev index ba4f907456..6891bc4159 100644 --- a/.env.docker-compose.dev +++ b/.env.docker-compose.dev @@ -1,18 +1,24 @@ # garage admin token for init script GARAGE_ADMIN_TOKEN=dev_admin_token -ASSETS_BUCKET_NAME=assets.v7.pubpub.org -ASSETS_UPLOAD_KEY=pubpubuser -ASSETS_UPLOAD_SECRET_KEY=pubpubpass -# set to same as above for s3fs/caddy to work -AWS_ACCESS_KEY_ID=pubpubuser -AWS_SECRET_ACCESS_KEY=pubpubpass - -ASSETS_REGION=garage +S3_BUCKET_NAME=assets.pubstar.org +S3_ACCESS_KEY=pubstaruser +S3_SECRET_KEY=pubstarpass +S3_REGION=us-east-1 # internal endpoint used by backend services running in Docker -ASSETS_STORAGE_ENDPOINT=http://garage:3900 +S3_ENDPOINT=http://minio:9000 # public endpoint used for signed URLs accessible from browsers -ASSETS_PUBLIC_ENDPOINT=http://localhost:3900 +S3_PUBLIC_ENDPOINT=http://localhost:9000 + +MINIO_ROOT_USER=pubstar-admin +MINIO_ROOT_PASSWORD=pubstar-admin + +S3_BACKUP_BUCKET=backups.pubstar.org +S3_BACKUP_ACCESS_KEY=pubstarbackupuser +S3_BACKUP_SECRET_KEY=pubstarbackuppass +S3_BACKUP_REGION=us-east-1 +S3_BACKUP_ENDPOINT=http://minio:9000 +S3_BACKUP_KEY_PREFIX=pg-backups POSTGRES_PORT=54322 POSTGRES_USER=postgres @@ -36,14 +42,17 @@ DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres JWT_SECRET=xxx -MAILGUN_SMTP_PASSWORD=xxx +SMTP_PASSWORD=xxx +SMTP_USERNAME=omitted +SMTP_HOST=inbucket +SMTP_PORT=2500 +SMTP_FROM=dev@pubstar.org +SMTP_FROM_NAME=Pubstar Team GCLOUD_KEY_FILE=xxx -MAILGUN_SMTP_HOST=inbucket -MAILGUN_SMTP_PORT=2500 # this needs to be localhost:54324 instead of inbucket:9000 bc we are almost always running the integration tests from outside the docker network INBUCKET_URL=http://localhost:54324 -MAILGUN_SMTP_USERNAME=omitted OTEL_SERVICE_NAME=core.core -PUBPUB_URL=http://localhost:3000 +PUBSTAR_HOSTNAME=http://localhost:3000 +PUBSTAR_URL=http://localhost:3000 API_KEY=xxx diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..dc6316c1b2 --- /dev/null +++ b/.env.example @@ -0,0 +1,55 @@ +# Base environment configuration +# Copy this to .env and customize as needed +# Values here are defaults that work across development, testing, and self-hosting + +# Database configuration +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +POSTGRES_PORT=54322 + +# Cache configuration +VALKEY_HOST=localhost +VALKEY_PORT=6379 + +# Minio configuration +MINIO_ROOT_USER=pubstar-admin +MINIO_ROOT_PASSWORD=pubstar-admin +S3_BUCKET_NAME=assets.pubpub.local +S3_ACCESS_KEY=pubpubuser +S3_SECRET_KEY=pubpubpass +S3_REGION=us-east-1 +# storage endpoint used for signed uploads and server-side s3 calls +S3_ENDPOINT=http://localhost:9000 +# optional public endpoint used for generated asset urls +# if hostname matches S3_BUCKET_NAME, urls are generated as: +# https://assets.pubstar.org/ +# otherwise they are generated as: +# // +# S3_PUBLIC_ENDPOINT=https://assets.pubstar.org + +# private backup storage config +S3_BACKUP_BUCKET=backups.pubstar.local +S3_BACKUP_ACCESS_KEY=pubstarbackupuser +S3_BACKUP_SECRET_KEY=pubstarbackuppass +S3_BACKUP_REGION=us-east-1 +S3_BACKUP_ENDPOINT=http://localhost:9000 +S3_BACKUP_KEY_PREFIX=pg-backups + +# Email configuration +SMTP_HOST=localhost +SMTP_PORT=54325 +SMTP_USERNAME=xxx +SMTP_PASSWORD=xxx + +# Application configuration +API_KEY=super_secret_key +PUBSTAR_URL=http://localhost:3000 + +# Other configuration +OTEL_SERVICE_NAME=pubstar-v7-dev +HONEYCOMB_API_KEY=xxx + +# Volume types (can be overridden per environment) +DB_VOLUME_TYPE=postgres_data +MINIO_VOLUME_TYPE=minio_data \ No newline at end of file diff --git a/.github/workflows/awsdeploy.yml b/.github/workflows/awsdeploy.yml deleted file mode 100644 index bcb7810763..0000000000 --- a/.github/workflows/awsdeploy.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - -name: aws ecs deploy - -on: - workflow_call: - inputs: - proper-name: - required: true - type: string - environment: - required: true - type: string - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - - #dispatch event means you can call it from the Github UI and set inputs on a form. - # MUST match the inputs of the workflow_call. - workflow_dispatch: - inputs: - proper-name: - required: true - type: string - environment: - required: true - type: string - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - -jobs: - deploy-core: - uses: ./.github/workflows/deploy-template.yml - with: - service: core - environment: ${{ inputs.environment }} - proper-name: ${{ inputs.proper-name }} - image-tag-override: ${{ inputs.image-tag-override }} - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - deploy-jobs: - uses: ./.github/workflows/deploy-template.yml - needs: deploy-core - with: - service: jobs - environment: ${{ inputs.environment }} - proper-name: ${{ inputs.proper-name }} - image-tag-override: ${{ inputs.image-tag-override }} - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - deploy-bastion: - uses: ./.github/workflows/deploy-template.yml - with: - service: bastion - environment: ${{ inputs.environment }} - proper-name: ${{ inputs.proper-name }} - repo-name-override: pubpub-v7 - image-tag-override: ${{ inputs.image-tag-override }} - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 48bc99a699..8716faa3c8 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -15,14 +15,14 @@ jobs: with: # necessary in order to show latest updates in docs fetch-depth: 0 - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 22.13.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 name: Install pnpm with: run_install: false @@ -34,7 +34,7 @@ jobs: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT - name: Setup pnpm cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ steps.get-store-path.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} @@ -42,7 +42,7 @@ jobs: ${{ runner.os }}-pnpm-store- # - name: Cache turbo - # uses: actions/cache@v4 + # uses: actions/cache@v5 # with: # path: .turbo # key: ${{ runner.os }}-turbo-${{ github.sha }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1ffe7ce2..8821e92e10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,77 +1,77 @@ -name: "CI" +name: 'CI' on: - workflow_call: - inputs: - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - workflow_dispatch: + workflow_call: + inputs: + image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 + type: string # use this to force a container version. + workflow_dispatch: env: - CI: true - AWS_REGION: us-east-1 + CI: true + AWS_REGION: us-east-1 - ECR_REPOSITORY_PREFIX: pubpub-v7 - CONTAINER_NAME: core + ECR_REPOSITORY_PREFIX: pubstar-v7 + CONTAINER_NAME: core jobs: - ci: - timeout-minutes: 15 - runs-on: ubuntu-latest - strategy: - matrix: - task: - - lint:ci - - type-check - - test-run - env: - COMPOSE_FILE: docker-compose.test.yml - ENV_FILE: .env.docker-compose.dev - steps: - - name: Checkout - uses: actions/checkout@v4 + ci: + timeout-minutes: 15 + runs-on: ubuntu-latest + strategy: + matrix: + task: + - lint:ci + - type-check + - test-run + env: + COMPOSE_FILE: docker-compose.test.yml + ENV_FILE: .env.docker-compose.dev + steps: + - name: Checkout + uses: actions/checkout@v6 - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 22.13.1 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.13.1 - - uses: pnpm/action-setup@v4 - name: Install pnpm - with: - run_install: false + - uses: pnpm/action-setup@v5 + name: Install pnpm + with: + run_install: false - - name: Get pnpm store directory - id: get-store-path - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + - name: Get pnpm store directory + id: get-store-path + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ steps.get-store-path.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- + - name: Setup pnpm cache + uses: actions/cache@v5 + with: + path: ${{ steps.get-store-path.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- - # to cache p:build, format, lint, type-check and test-run - - name: Setup turbo cache - uses: actions/cache@v4 - with: - path: .turbo - key: ${{ runner.os }}-turbo-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-turbo- + # to cache p:build, format, lint, type-check and test-run + - name: Setup turbo cache + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- - - name: Install dependencies - run: pnpm install --frozen-lockfile --prefer-offline + - name: Install dependencies + run: pnpm install --frozen-lockfile --prefer-offline - - name: p:build - run: pnpm p:build + - name: p:build + run: pnpm p:build - - name: Setup test dependencies - if: matrix.task == 'test-run' - run: pnpm test:setup + - name: Setup test dependencies + if: matrix.task == 'test-run' + run: pnpm test:setup - - name: Run task - run: NODE_ENV=test pnpm ${{ matrix.task }} + - name: Run task + run: NODE_ENV=test pnpm ${{ matrix.task }} diff --git a/.github/workflows/deploy-manual.yml b/.github/workflows/deploy-manual.yml new file mode 100644 index 0000000000..fe4659fdfc --- /dev/null +++ b/.github/workflows/deploy-manual.yml @@ -0,0 +1,82 @@ +name: Manual Deploy + +on: + workflow_dispatch: + inputs: + environment: + description: "Target environment" + required: true + type: choice + options: + - sandbox + - production + image_tag: + description: "Existing image tag to deploy (leave empty to build from current branch)" + required: false + type: string + +permissions: + id-token: write + contents: read + packages: write + # pull-requests: write + +concurrency: + group: deploy-${{ inputs.environment == 'production' && 'pubstar' || 'sandbox' }} + cancel-in-progress: false + +jobs: + build-all: + if: inputs.image_tag == '' + permissions: + contents: read + packages: write + uses: ./.github/workflows/ghcr-build-all.yml + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + deploy-sandbox: + if: always() && inputs.environment == 'sandbox' && (needs.build-all.result == 'success' || needs.build-all.result == 'skipped') + needs: build-all + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: ${{ inputs.image_tag || github.sha }} + stack_name: sandbox + hostname: sandbox.pubstar.org + env_file: .env.sandbox.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + + + deploy-prod: + if: always() && inputs.environment == 'production' && (needs.build-all.result == 'success' || needs.build-all.result == 'skipped') + needs: build-all + permissions: + contents: read + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: ${{ inputs.image_tag || github.sha }} + stack_name: pubstar + hostname: app.pubstar.org + env_file: .env.enc + stack_file: stack.yml + uses_gateway: false + ssh_host_secret: SSH_HOST_PROD + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PROD }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} diff --git a/.github/workflows/deploy-stack.yml b/.github/workflows/deploy-stack.yml new file mode 100644 index 0000000000..64636c3ead --- /dev/null +++ b/.github/workflows/deploy-stack.yml @@ -0,0 +1,349 @@ +name: Deploy Stack + +on: + workflow_call: + inputs: + action: + required: true + type: string + description: "'deploy' or 'teardown'" + image_tag: + required: false + type: string + description: "image tag to deploy" + stack_name: + required: true + type: string + description: "docker swarm stack name" + hostname: + required: true + type: string + description: "public hostname for this deployment" + env_file: + required: true + type: string + description: "sops-encrypted env file to decrypt (relative to infra/)" + stack_file: + required: true + type: string + description: "compose file to deploy (e.g. stack.yml or stack.preview.yml)" + uses_gateway: + required: true + type: boolean + description: "if true, deploy behind the shared Caddy gateway" + ssh_host_secret: + required: true + type: string + description: "name of the SSH_HOST secret to use (value passed as secret)" + secrets: + SSH_PRIVATE_KEY: + required: true + SSH_USER: + required: true + SSH_HOST: + required: true + GHCR_USER: + required: true + GHCR_TOKEN: + required: true + +permissions: + contents: read + # pull-requests: write + +jobs: + deploy: + if: inputs.action == 'deploy' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + + - name: Start SSH agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Add known hosts + run: | + mkdir -p ~/.ssh + ssh-keyscan -H "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts + + - name: Deploy stack + env: + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.head_ref || github.ref_name }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + IMAGE_TAG: ${{ inputs.image_tag }} + STACK_NAME: ${{ inputs.stack_name }} + DEPLOY_HOST: ${{ inputs.hostname }} + ENV_FILE: ${{ inputs.env_file }} + STACK_FILE: ${{ inputs.stack_file }} + USES_GATEWAY: ${{ inputs.uses_gateway }} + run: | + ssh "${SSH_USER}@${SSH_HOST}" \ + "env GHCR_USER='${GHCR_USER}' GHCR_TOKEN='${GHCR_TOKEN}' IMAGE_TAG='${IMAGE_TAG}' STACK_NAME='${STACK_NAME}' DEPLOY_HOST='${DEPLOY_HOST}' ENV_FILE='${ENV_FILE}' STACK_FILE='${STACK_FILE}' USES_GATEWAY='${USES_GATEWAY}' bash -s -- '${REPO}' '${BRANCH}'" <<'EOS' + set -euo pipefail + + REPO="${1:?missing repo}" + BRANCH="${2:-main}" + + : "${IMAGE_TAG:?missing IMAGE_TAG}" + : "${GHCR_USER:?missing GHCR_USER}" + : "${GHCR_TOKEN:?missing GHCR_TOKEN}" + : "${STACK_NAME:?missing STACK_NAME}" + : "${DEPLOY_HOST:?missing DEPLOY_HOST}" + : "${ENV_FILE:?missing ENV_FILE}" + : "${STACK_FILE:?missing STACK_FILE}" + + ASSETS_HOST="$DEPLOY_HOST" + if [[ "$DEPLOY_HOST" =~ ^pr-([0-9]+)\.pubstar\.org$ ]]; then + ASSETS_HOST="pr-${BASH_REMATCH[1]}-assets.pubstar.org" + elif [[ "$DEPLOY_HOST" == "sandbox.pubstar.org" ]]; then + ASSETS_HOST="sandbox-assets.pubstar.org" + fi + + REPO_NAME="${REPO##*/}" + APP_DIR="/srv/${STACK_NAME}/${REPO_NAME}" + REPO_SSH="git@github.com:${REPO}.git" + + LOCK_DIR="/tmp/deploy-locks" + LOCK_FILE="${LOCK_DIR}/${STACK_NAME}.lock" + LOCK_MAX_AGE=900 + mkdir -p "$LOCK_DIR" + + acquire_lock() { + if [[ -f "$LOCK_FILE" ]]; then + local lock_pid lock_age + lock_pid="$(head -1 "$LOCK_FILE" 2>/dev/null || echo "")" + + if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then + lock_age=$(( $(date +%s) - $(stat -c %Y "$LOCK_FILE" 2>/dev/null || echo "0") )) + + if (( lock_age > LOCK_MAX_AGE )); then + echo "stale lock held by pid $lock_pid for ${lock_age}s, removing" + rm -f "$LOCK_FILE" + else + echo "::error::another deploy to ${STACK_NAME} is in progress (pid $lock_pid, age ${lock_age}s)" + return 1 + fi + else + echo "lock owner (pid ${lock_pid:-?}) is gone, cleaning up stale lock" + rm -f "$LOCK_FILE" + fi + fi + + echo "$$" > "$LOCK_FILE" + } + + release_lock() { + rm -f "$LOCK_FILE" + } + + trap release_lock EXIT + + if ! acquire_lock; then + exit 1 + fi + + ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2>/dev/null + chmod 600 ~/.ssh/known_hosts + + if [[ ! -d "${APP_DIR}/.git" ]]; then + sudo mkdir -p "${APP_DIR}" + sudo chown -R "$USER:$USER" "${APP_DIR}" + git clone --branch "${BRANCH}" "${REPO_SSH}" "${APP_DIR}" + fi + + cd "${APP_DIR}" + git fetch --prune --tags origin + git checkout --detach "origin/${BRANCH}" + + cd infra + umask 077 + + sops -d --input-type dotenv --output-type dotenv "$ENV_FILE" > ".env.${STACK_NAME}" + + if ! sudo docker info --format '{{.Swarm.LocalNodeState}}' | grep -qx active; then + sudo docker swarm init --advertise-addr "$(hostname -I | awk '{print $1}')" + fi + + echo "$GHCR_TOKEN" | sudo docker login ghcr.io -u "$GHCR_USER" --password-stdin + + sudo docker pull ghcr.io/knowledgefutures/platform:"$IMAGE_TAG" + + if [[ "$USES_GATEWAY" == "true" ]]; then + echo "deploying gateway stack..." + sudo docker stack deploy -c stack.gateway.yml --prune gateway + + for attempt in 1 2 3 4 5; do + if sudo docker service update --force gateway_proxy; then + break + fi + echo "gateway_proxy update attempt $attempt failed, retrying in 5s..." + sleep 5 + done + + SUFFIX="${STACK_NAME##*-}" + if [[ "$SUFFIX" =~ ^[0-9]+$ ]]; then + PORT_NUM="$SUFFIX" + else + PORT_NUM="9000" + fi + + PLATFORM_PORT="1${PORT_NUM}" + BUILDER_PORT="2${PORT_NUM}" + MINIO_PORT="3${PORT_NUM}" + MINIO_CONSOLE_PORT="4${PORT_NUM}" + INBUCKET_PORT="5${PORT_NUM}" + MOCK_NOTIFY_PORT="6${PORT_NUM}" + + sudo env IMAGE_TAG="$IMAGE_TAG" STACK_NAME="$STACK_NAME" DEPLOY_HOST="$DEPLOY_HOST" ASSETS_HOST="$ASSETS_HOST" \ + PLATFORM_PORT="$PLATFORM_PORT" BUILDER_PORT="$BUILDER_PORT" \ + MINIO_PORT="$MINIO_PORT" MINIO_CONSOLE_PORT="$MINIO_CONSOLE_PORT" \ + INBUCKET_PORT="$INBUCKET_PORT" MOCK_NOTIFY_PORT="$MOCK_NOTIFY_PORT" \ + docker stack deploy -c "$STACK_FILE" \ + --with-registry-auth --resolve-image always --prune "$STACK_NAME" + + else + echo "deploying with IMAGE_TAG=$IMAGE_TAG" + + sudo env IMAGE_TAG="$IMAGE_TAG" STACK_NAME="$STACK_NAME" ASSETS_HOST="$ASSETS_HOST" \ + docker stack deploy -c "$STACK_FILE" \ + --with-registry-auth --resolve-image always --prune "$STACK_NAME" + fi + + sudo docker stack services "$STACK_NAME" + sudo docker image prune -f + + dump_failure_info() { + local svc="$1" + echo "" + echo "::group::service inspect ($svc)" + sudo docker service inspect "$svc" --pretty 2>&1 || true + echo "::endgroup::" + + echo "::group::task history ($svc)" + sudo docker service ps "$svc" --no-trunc 2>&1 || true + echo "::endgroup::" + + echo "::group::recent logs ($svc)" + sudo docker service logs "$svc" --tail 200 --no-task-ids --timestamps 2>&1 || true + echo "::endgroup::" + } + + wait_rollout() { + local svc="$1" + local timeout="${2:-600}" + local end=$((SECONDS + timeout)) + + echo "waiting for rollout of $svc (timeout ${timeout}s)..." + + while (( SECONDS < end )); do + local desired running state + desired="$(sudo docker service inspect "$svc" --format '{{.Spec.Mode.Replicated.Replicas}}' 2>/dev/null || echo "")" + running="$(sudo docker service ps "$svc" --filter desired-state=running --format '{{.CurrentState}}' 2>/dev/null | grep -c '^Running' || true)" + state="$(sudo docker service inspect "$svc" --format '{{if .UpdateStatus}}{{.UpdateStatus.State}}{{end}}' 2>/dev/null || echo "")" + + echo " $svc: desired=$desired running=$running update_state=$state" + + if [[ "$state" == "rollback_started" || "$state" == "rollback_completed" ]]; then + echo "::error::rollback detected for $svc (state=$state)" + dump_failure_info "$svc" + return 1 + fi + + if [[ -n "$desired" && "$running" == "$desired" ]] && { [[ -z "$state" ]] || [[ "$state" == "completed" ]]; }; then + echo " $svc rollout complete" + return 0 + fi + + sleep 5 + done + + echo "::error::rollout timeout for $svc after ${timeout}s" + dump_failure_info "$svc" + return 1 + } + + wait_rollout "${STACK_NAME}_platform" 600 + + EOS + + # - name: Comment on PR + # if: github.event_name == 'pull_request' + # uses: actions/github-script@v7 + # with: + # script: | + # const body = `Preview deployed at https://${{ inputs.hostname }}`; + # const { data: comments } = await github.rest.issues.listComments({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # issue_number: context.issue.number, + # }); + # const existing = comments.find(c => c.body.includes('Preview deployed at')); + # if (existing) { + # await github.rest.issues.updateComment({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # comment_id: existing.id, + # body, + # }); + # } else { + # await github.rest.issues.createComment({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # issue_number: context.issue.number, + # body, + # }); + # } + + teardown: + if: inputs.action == 'teardown' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Start SSH agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Add known hosts + run: | + mkdir -p ~/.ssh + ssh-keyscan -H "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts + + - name: Teardown stack + env: + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST }} + STACK_NAME: ${{ inputs.stack_name }} + USES_GATEWAY: ${{ inputs.uses_gateway }} + run: | + ssh "${SSH_USER}@${SSH_HOST}" \ + "env STACK_NAME='${STACK_NAME}' USES_GATEWAY='${USES_GATEWAY}' bash -s" <<'EOS' + set -euo pipefail + : "${STACK_NAME:?missing STACK_NAME}" + + echo "tearing down stack $STACK_NAME" + + if sudo docker stack ls --format '{{.Name}}' | grep -qx "$STACK_NAME"; then + sudo docker stack rm "$STACK_NAME" + sleep 10 + sudo docker volume ls --filter "label=com.docker.stack.namespace=$STACK_NAME" -q \ + | xargs -r sudo docker volume rm || true + echo "stack $STACK_NAME removed" + else + echo "stack $STACK_NAME not found, nothing to tear down" + fi + + if [[ "$USES_GATEWAY" == "true" ]]; then + echo "gateway uses dynamic map routing, no per-site cleanup needed" + fi + + sudo docker image prune -f + + EOS diff --git a/.github/workflows/deploy-template.yml b/.github/workflows/deploy-template.yml deleted file mode 100644 index d98a0370c8..0000000000 --- a/.github/workflows/deploy-template.yml +++ /dev/null @@ -1,137 +0,0 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - -name: aws ecs deploy template - -on: - workflow_call: - inputs: - service: # example: core - required: true - type: string - proper-name: # example: blake - required: true - type: string - environment: # example: staging - required: true - type: string - repo-name-override: - type: string - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - workflow_dispatch: - inputs: - service: # example: core - required: true - type: string - proper-name: # example: blake - required: true - type: string - environment: # example: staging - required: true - type: string - repo-name-override: - type: string - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - -env: - AWS_REGION: us-east-1 - ECR_REPOSITORY_PREFIX: pubpub-v7 - ECR_REPOSITORY_NAME_OVERRIDE: ${{ inputs.repo-name-override }} - ECS_SERVICE: ${{ inputs.proper-name }}-${{inputs.service}} - ECS_CLUSTER: ${{inputs.proper-name}}-ecs-cluster-${{inputs.environment}} - ECS_TASK_DEFINITION_TEMPLATE: ${{ inputs.proper-name }}-${{inputs.service}} - CONTAINER_NAME: ${{inputs.service}} - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - environment: ${{ inputs.proper-name }}-${{ inputs.environment }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ vars.IAM_ROLE_TO_ASSUME }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Get image tag based on SHA - id: gettag - env: - OVERRIDE: ${{inputs.image-tag-override}} - # use shell substitution - run: echo "tag=${OVERRIDE:-$(git describe --always --abbrev=40 --dirty)}" >> $GITHUB_OUTPUT - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Retrieve Task Definition contents from template - id: get-taskdef - run: | - aws ecs describe-task-definition \ - --task-definition $ECS_TASK_DEFINITION_TEMPLATE \ - --query taskDefinition >> template_task_def.json - - - name: Get image labels - id: label - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.gettag.outputs.tag }} - run: | - echo "label=$ECR_REGISTRY/${ECR_REPOSITORY_NAME_OVERRIDE:-$ECR_REPOSITORY_PREFIX-${CONTAINER_NAME}}:$IMAGE_TAG" >> $GITHUB_OUTPUT - echo "base_label=$ECR_REGISTRY/$ECR_REPOSITORY_PREFIX:$IMAGE_TAG" >> $GITHUB_OUTPUT - - - name: Fill in the new image ID in the Amazon ECS task definition - id: task-def-service - uses: aws-actions/amazon-ecs-render-task-definition@c804dfbdd57f713b6c079302a4c01db7017a36fc - with: - task-definition: template_task_def.json - container-name: ${{ env.CONTAINER_NAME }} - image: ${{ steps.label.outputs.label }} - - # Complication when the number of containers in the task are unknown: - # we have to know where to get the inputs for each step, including the upload - # step. - - name: Fill in the new image ID in the Amazon ECS task definition for migrations - id: task-def-migration - if: inputs.service == 'core' - uses: aws-actions/amazon-ecs-render-task-definition@c804dfbdd57f713b6c079302a4c01db7017a36fc - with: - task-definition: ${{ steps.task-def-service.outputs.task-definition }} - container-name: migrations - image: ${{ steps.label.outputs.base_label }} - - - name: Deploy Amazon ECS task definition - id: deploy-service-only - # This one is different. The single-image case is when not deploying core. - if: inputs.service != 'core' - uses: aws-actions/amazon-ecs-deploy-task-definition@16f052ed696e6e5bf88c208a8e5ba1af7ced3310 - with: - # it is because of this line that the two steps need different if conditions - task-definition: ${{ steps.task-def-service.outputs.task-definition }} - service: ${{ env.ECS_SERVICE }} - cluster: ${{ env.ECS_CLUSTER }} - wait-for-service-stability: true - - - name: Deploy Amazon ECS task definition including migrations - id: deploy-service-and-migrations - if: inputs.service == 'core' - uses: aws-actions/amazon-ecs-deploy-task-definition@16f052ed696e6e5bf88c208a8e5ba1af7ced3310 - with: - # it is because of this line that the two steps need different if conditions - task-definition: ${{ steps.task-def-migration.outputs.task-definition }} - service: ${{ env.ECS_SERVICE }} - cluster: ${{ env.ECS_CLUSTER }} - wait-for-service-stability: true diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 69cea2d379..10735368a5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,26 +1,21 @@ on: workflow_call: inputs: - image-tag-override: # example: latest, 7037e37a18a379d583164441baff9e594cc479f8 - type: string # use this to force a container version. - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true + image-tag-override: + type: string env: CI: true - AWS_REGION: us-east-1 - - ECR_REPOSITORY_PREFIX: pubpub-v7 - CONTAINER_NAME: core jobs: integration-tests: name: Integration tests runs-on: ubuntu-latest + permissions: + contents: read + packages: read + strategy: matrix: package: @@ -31,16 +26,16 @@ jobs: ENV_FILE: .env.docker-compose.dev steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 22.13.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 name: Install pnpm with: run_install: false @@ -52,7 +47,6 @@ jobs: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT - name: Setup pnpm cache - # since this always runs after CI, there's no need to save the cache afterwards, since it's guaranteed to be the same uses: actions/cache/restore@v4 with: path: ${{ steps.get-store-path.outputs.STORE_PATH }} @@ -60,7 +54,6 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - # mostly to skip preconstruct build - name: Setup turbo cache uses: actions/cache/restore@v4 with: @@ -69,15 +62,7 @@ jobs: restore-keys: | ${{ runner.os }}-turbo- - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ vars.IAM_ROLE_TO_ASSUME }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Get image tag based on SHA + - name: Get image tag id: gettag run: | if [ -n "${{ inputs.image-tag-override }}" ]; then @@ -88,19 +73,21 @@ jobs: echo "Using current SHA as image tag" fi - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Get image labels + - name: Compute image refs id: label env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.gettag.outputs.tag }} run: | - echo "core_label=$ECR_REGISTRY/${ECR_REPOSITORY_NAME_OVERRIDE:-$ECR_REPOSITORY_PREFIX-core}:$IMAGE_TAG" >> $GITHUB_OUTPUT - echo "jobs_label=$ECR_REGISTRY/${ECR_REPOSITORY_NAME_OVERRIDE:-$ECR_REPOSITORY_PREFIX-jobs}:$IMAGE_TAG" >> $GITHUB_OUTPUT - echo "base_label=$ECR_REGISTRY/$ECR_REPOSITORY_PREFIX:$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "core_label=ghcr.io/knowledgefutures/platform:$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "jobs_label=ghcr.io/knowledgefutures/platform-jobs:$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "site_builder_label=ghcr.io/knowledgefutures/platform-site-builder:$IMAGE_TAG" >> $GITHUB_OUTPUT - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline @@ -114,7 +101,6 @@ jobs: - name: Run migrations and seed run: pnpm --filter core reset-base env: - # 20241126: this prevents the arcadia seed from running, which contains a ton of pubs which potentially might slow down the tests MINIMAL_SEED: true SKIP_VALIDATION: true @@ -123,15 +109,16 @@ jobs: - name: Start up core etc run: pnpm integration:setup env: - INTEGRATION_TESTS_IMAGE: ${{steps.label.outputs.core_label}} - JOBS_IMAGE: ${{steps.label.outputs.jobs_label}} + INTEGRATION_TESTS_IMAGE: ${{ steps.label.outputs.core_label }} + JOBS_IMAGE: ${{ steps.label.outputs.jobs_label }} + SITE_BUILDER_IMAGE: ${{ steps.label.outputs.site_builder_label }} - name: Log out Container ID for health check id: log-container-id run: echo "CONTAINER_ID=$(docker compose -f docker-compose.test.yml ps integration-tests -q)" >> $GITHUB_OUTPUT - name: Wait until container is healthy - run: while [ "`docker inspect -f {{.State.Health.Status}} ${{steps.log-container-id.outputs.CONTAINER_ID}}`" != "healthy" ]; do sleep .2; done + run: while [ "`docker inspect -f {{.State.Health.Status}} ${{ steps.log-container-id.outputs.CONTAINER_ID }}`" != "healthy" ]; do sleep .2; done - name: Run integration tests run: pnpm playwright:test --filter ${{ matrix.package }} --env-mode=loose @@ -141,11 +128,12 @@ jobs: DATABASE_URL: postgresql://postgres:postgres@localhost:54322/postgres - name: Print container logs - if: ${{failure() || cancelled()}} + if: ${{ failure() || cancelled() }} run: docker compose -f docker-compose.test.yml --profile integration logs -t env: INTEGRATION_TESTS_IMAGE: ${{steps.label.outputs.core_label}} JOBS_IMAGE: ${{steps.label.outputs.jobs_label}} + SITE_BUILDER_IMAGE: ${{steps.label.outputs.site_builder_label}} - name: Upload core playwright snapshots artifact if: failure() && matrix.package == 'core' diff --git a/.github/workflows/ecrbuild-all.yml b/.github/workflows/ecrbuild-all.yml deleted file mode 100644 index 1599ae2bdd..0000000000 --- a/.github/workflows/ecrbuild-all.yml +++ /dev/null @@ -1,76 +0,0 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - -name: docker build to ECR - -on: - workflow_call: - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - inputs: - publish_to_ghcr: - type: boolean - default: false - outputs: - core-image: - description: "Core image SHA" - value: ${{ jobs.build-core.outputs.image-sha }} - base-image: - description: "Base image SHA" - value: ${{ jobs.build-base.outputs.image-sha }} - jobs-image: - description: "Jobs image SHA" - value: ${{ jobs.build-jobs.outputs.image-sha }} - -jobs: - emit-sha-tag: - name: Emit container tag sha - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Get image tag - id: label - run: | - sha_short=$(git describe --always --abbrev=40 --dirty) - echo "Building containers with tag:" - echo "$sha_short" - - build-base: - uses: ./.github/workflows/ecrbuild-template.yml - with: - publish_to_ghcr: ${{ inputs.publish_to_ghcr }} - ghcr_image_name: platform-migrations - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - build-core: - uses: ./.github/workflows/ecrbuild-template.yml - # needs: - # - build-base - with: - package: core - publish_to_ghcr: ${{ inputs.publish_to_ghcr }} - ghcr_image_name: platform - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - build-jobs: - uses: ./.github/workflows/ecrbuild-template.yml - # needs: - # - build-base - with: - package: jobs - target: jobs - publish_to_ghcr: ${{ inputs.publish_to_ghcr }} - ghcr_image_name: platform-jobs - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - diff --git a/.github/workflows/ecrbuild-template.yml b/.github/workflows/ecrbuild-template.yml deleted file mode 100644 index 4fc87f6027..0000000000 --- a/.github/workflows/ecrbuild-template.yml +++ /dev/null @@ -1,143 +0,0 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - -name: aws ecr build template - -on: - workflow_call: - inputs: - package: - type: string - runner: - type: string - default: ubuntu-latest - target: - type: string - publish_to_ghcr: - type: boolean - default: false - ghcr_image_name: - type: string - required: false - outputs: - image-sha: - description: "Image SHA" - value: ${{ jobs.build.outputs.image-sha }} - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - -env: - PACKAGE: ${{ inputs.package }} - AWS_REGION: us-east-1 # set this to your preferred AWS region, e.g. us-west-1 - ECR_REPOSITORY_PREFIX: pubpub-v7 # set this to your Amazon ECR repository name - TARGET: ${{ inputs.target }} - -jobs: - build: - name: Build - runs-on: ${{ inputs.runner }} - outputs: - image-sha: ${{ steps.label.outputs.label }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ vars.IAM_ROLE_TO_ASSUME }} - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # necessary in order to upload build source maps to sentry - - name: Get sentry token - id: sentry-token - uses: aws-actions/aws-secretsmanager-get-secrets@v2 - with: - secret-ids: | - SENTRY_AUTH_TOKEN, ${{ vars.SENTRY_AUTH_TOKEN_ARN }} - - - name: setup docker buildx - uses: docker/setup-buildx-action@v3 - - - name: Create and use a new builder instance - run: | - docker buildx create --name cached-builder --use - - - name: Get image label - id: label - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - run: | - sha_short=$(git describe --always --abbrev=40 --dirty) - if [[ -z $PACKAGE ]] - then - package_suffix="" - echo "target=monorepo" >> $GITHUB_OUTPUT - else - package_suffix="-${PACKAGE}" - echo "target=${TARGET:-next-app-${PACKAGE}}" >> $GITHUB_OUTPUT - fi - echo "label=$ECR_REGISTRY/$ECR_REPOSITORY_PREFIX$package_suffix:$sha_short" >> $GITHUB_OUTPUT - if [[ ${{ inputs.publish_to_ghcr }} == "true" && -n ${{ inputs.ghcr_image_name }} ]] - then - TIMESTAMP=$(date +%Y%m%d-%H%M%S) - - echo "ghcr_latest_label=ghcr.io/pubpub/${{ inputs.ghcr_image_name }}:latest" >> $GITHUB_OUTPUT - - echo "ghcr_sha_label=ghcr.io/pubpub/${{ inputs.ghcr_image_name }}:$sha_short" >> $GITHUB_OUTPUT - - echo "ghcr_timestamp_label=ghcr.io/pubpub/${{ inputs.ghcr_image_name }}:$TIMESTAMP" >> $GITHUB_OUTPUT - fi - - - name: Check if SENTRY_AUTH_TOKEN is set - run: | - if [[ -z ${{ env.SENTRY_AUTH_TOKEN }} ]] - then - echo "SENTRY_AUTH_TOKEN is not set" - exit 1 - fi - - - name: Build, tag, and push image to Amazon ECR - uses: docker/build-push-action@v6 - id: build-image - env: - REGISTRY_REF: ${{steps.login-ecr.outputs.registry}}/${{env.ECR_REPOSITORY_PREFIX}}-${{env.PACKAGE}}:cache - LABEL: ${{ steps.label.outputs.label }} - TARGET: ${{ steps.label.outputs.target }} - SENTRY_AUTH_TOKEN: ${{ env.SENTRY_AUTH_TOKEN }} - with: - context: . - # cache-from: type=registry,ref=${{env.REGISTRY_REF}} - # cache-to: type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${{env.REGISTRY_REF}} - builder: cached-builder - build-args: | - PACKAGE=${{ inputs.package }} - CI=true - secrets: | - SENTRY_AUTH_TOKEN=${{ env.SENTRY_AUTH_TOKEN }} - target: ${{ steps.label.outputs.target }} - tags: | - ${{ steps.label.outputs.label }} - ${{ steps.label.outputs.ghcr_latest_label }} - ${{ steps.label.outputs.ghcr_sha_label }} - ${{ steps.label.outputs.ghcr_timestamp_label }} - platforms: linux/amd64 - push: true diff --git a/.github/workflows/ghcr-build-all.yml b/.github/workflows/ghcr-build-all.yml new file mode 100644 index 0000000000..1b63f91879 --- /dev/null +++ b/.github/workflows/ghcr-build-all.yml @@ -0,0 +1,72 @@ +name: docker build to GHCR + +on: + workflow_call: + inputs: + publish_latest: + type: boolean + default: false + image_tag: + type: string + description: "Override the image tag (e.g. v1.2.3). Falls back to git describe." + outputs: + core-image: + description: 'Core image ref' + value: ${{ jobs.build-core.outputs.image-sha }} + jobs-image: + description: 'Jobs image ref' + value: ${{ jobs.build-jobs.outputs.image-sha }} + site-builder-image: + description: 'Site builder image ref' + value: ${{ jobs.build-site-builder.outputs.image-sha }} + coar-server-image: + description: 'Coar server image ref' + value: ${{ jobs.build-coar-server.outputs.image-sha }} + secrets: + SENTRY_AUTH_TOKEN: + required: true + +jobs: + build-core: + uses: ./.github/workflows/ghcr-build-template.yml + with: + package: core + ghcr_image_name: platform + publish_latest: ${{ inputs.publish_latest }} + image_tag: ${{ inputs.image_tag }} + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + build-jobs: + uses: ./.github/workflows/ghcr-build-template.yml + with: + package: jobs + target: jobs + ghcr_image_name: platform-jobs + publish_latest: ${{ inputs.publish_latest }} + image_tag: ${{ inputs.image_tag }} + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + build-site-builder: + uses: ./.github/workflows/ghcr-build-template.yml + with: + package: site-builder-2 + target: jobs + ghcr_image_name: platform-site-builder + publish_latest: ${{ inputs.publish_latest }} + image_tag: ${{ inputs.image_tag }} + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + + build-coar-server: + uses: ./.github/workflows/ghcr-build-template.yml + with: + package: mock-notify + base_path: /mock-notify + ghcr_image_name: mock-coar-notify-server + publish_latest: ${{ inputs.publish_latest }} + image_tag: ${{ inputs.image_tag }} + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ghcr-build-template.yml b/.github/workflows/ghcr-build-template.yml new file mode 100644 index 0000000000..1daaf6c96f --- /dev/null +++ b/.github/workflows/ghcr-build-template.yml @@ -0,0 +1,100 @@ +name: ghcr build template + +on: + workflow_call: + inputs: + package: + type: string + runner: + type: string + default: ubuntu-latest + target: + type: string + ghcr_image_name: + type: string + required: true + publish_latest: + type: boolean + default: false + image_tag: + type: string + description: "Override the image tag (e.g. v1.2.3). Falls back to git describe." + base_path: + type: string + default: "" + description: "Base path for Next.js apps (e.g. /mock-notify). Baked into the build." + outputs: + image-sha: + description: 'Full GHCR image ref with SHA tag' + value: ${{ jobs.build.outputs.image-sha }} + secrets: + SENTRY_AUTH_TOKEN: + required: true + +jobs: + build: + name: Build + runs-on: ${{ inputs.runner }} + permissions: + contents: read + packages: write + + outputs: + image-sha: ${{ steps.label.outputs.label }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Compute image tags + id: label + run: | + sha_short="${{ inputs.image_tag || '' }}" + if [[ -z "$sha_short" ]]; then + sha_short=$(git describe --always --abbrev=40 --dirty) + fi + + if [[ -z "${{ inputs.package }}" ]]; then + echo "target=monorepo" >> $GITHUB_OUTPUT + else + echo "target=${{ inputs.target || format('next-app-{0}', inputs.package) }}" >> $GITHUB_OUTPUT + fi + + echo "label=ghcr.io/knowledgefutures/${{ inputs.ghcr_image_name }}:$sha_short" >> $GITHUB_OUTPUT + + TAGS="ghcr.io/knowledgefutures/${{ inputs.ghcr_image_name }}:$sha_short" + if [[ "${{ inputs.publish_latest }}" == "true" ]]; then + TAGS="$TAGS,ghcr.io/knowledgefutures/${{ inputs.ghcr_image_name }}:latest" + fi + echo "tags=$TAGS" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v7 + with: + context: . + cache-from: type=registry,ref=ghcr.io/knowledgefutures/${{ inputs.ghcr_image_name }}:buildcache + cache-to: type=registry,ref=ghcr.io/knowledgefutures/${{ inputs.ghcr_image_name }}:buildcache,mode=max + build-args: | + PACKAGE=${{ inputs.package }} + CI=true + BASE_PATH=${{ inputs.base_path }} + secrets: | + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + target: ${{ steps.label.outputs.target }} + tags: ${{ steps.label.outputs.tags }} + platforms: linux/amd64 + push: true + provenance: false + sbom: false diff --git a/.github/workflows/on_main.yml b/.github/workflows/on_main.yml deleted file mode 100644 index 2a91d6e728..0000000000 --- a/.github/workflows/on_main.yml +++ /dev/null @@ -1,74 +0,0 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - -name: Promote from main - -on: - push: - branches: - - main - -jobs: - ci: - uses: ./.github/workflows/ci.yml - - build-all: - needs: ci - uses: ./.github/workflows/ecrbuild-all.yml - with: - publish_to_ghcr: true - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - run-e2e: - needs: - - ci - - build-all - uses: ./.github/workflows/e2e.yml - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - deploy-all: - uses: ./.github/workflows/awsdeploy.yml - needs: - - build-all - - run-e2e - with: - proper-name: stevie - environment: production - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - - deploy-docs: - permissions: - contents: write - pages: write - pull-requests: write - uses: ./.github/workflows/build-docs.yml - with: - preview: false - - deploy-preview: - uses: ./.github/workflows/pull-preview.yml - needs: - - build-all - permissions: - contents: read - deployments: write - pull-requests: write - statuses: write - with: - PLATFORM_IMAGE: ${{ needs.build-all.outputs.core-image }} - JOBS_IMAGE: ${{ needs.build-all.outputs.jobs-image }} - MIGRATIONS_IMAGE: ${{ needs.build-all.outputs.base-image }} - AWS_REGION: "us-east-1" - ALWAYS_ON: "main" - COMPOSE_FILES: docker-compose.preview.sandbox.yml - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GH_PAT_PR_PREVIEW_CLEANUP: ${{ secrets.GH_PAT_PR_PREVIEW_CLEANUP }} - PREVIEW_DATACITE_REPOSITORY_ID: ${{ secrets.PREVIEW_DATACITE_REPOSITORY_ID }} - PREVIEW_DATACITE_PASSWORD: ${{ secrets.PREVIEW_DATACITE_PASSWORD }} diff --git a/.github/workflows/on_main_2.yml b/.github/workflows/on_main_2.yml new file mode 100644 index 0000000000..1d04d2d89f --- /dev/null +++ b/.github/workflows/on_main_2.yml @@ -0,0 +1,69 @@ +name: Promote from main (new) + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + id-token: write + contents: read + packages: write + pull-requests: write + +concurrency: + group: deploy-sandbox + cancel-in-progress: false + +jobs: + ci: + uses: ./.github/workflows/ci.yml + + build-all: + needs: ci + permissions: + contents: read + packages: write + uses: ./.github/workflows/ghcr-build-all.yml + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + with: + publish_latest: true + + run-e2e: + needs: + - ci + - build-all + uses: ./.github/workflows/e2e.yml + + deploy-sandbox: + needs: build-all + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: ${{ github.sha }} + stack_name: sandbox + hostname: sandbox.pubstar.org + env_file: .env.sandbox.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + + deploy-docs: + permissions: + contents: write + pages: write + pull-requests: write + uses: ./.github/workflows/build-docs.yml + with: + preview: false diff --git a/.github/workflows/on_pr.yml b/.github/workflows/on_pr.yml index 9d202b4e3a..28268361fb 100644 --- a/.github/workflows/on_pr.yml +++ b/.github/workflows/on_pr.yml @@ -1,26 +1,28 @@ -# Based on https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service - name: PR Updated triggers on: pull_request: types: [labeled, unlabeled, synchronize, closed, reopened, opened] -env: - AWS_REGION: us-east-1 - permissions: id-token: write contents: read + packages: write + pull-requests: write + +concurrency: + group: pr-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: + # check if the docs are modified path-filter: runs-on: ubuntu-latest - if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' || github.event.action == 'closed' + if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' || github.event.action == 'closed' || github.event.action == 'labeled' || github.event.action == 'unlabeled' outputs: docs: ${{ steps.changes.outputs.docs }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: dorny/paths-filter@v3 @@ -30,15 +32,14 @@ jobs: docs: - 'docs/**' - - # you can skip the build by adding 'skip-build' to the commit message, useful when testing tests + # you can skip the build by adding 'skip-build' to the commit message, useful when testing e2e tests or experimenting with the deploy skip_build_sha: outputs: last-successful-build-sha: ${{ steps.last-build.outputs.last-successful-build-sha }} runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -62,20 +63,17 @@ jobs: run: | pr_number="${{ github.event.pull_request.number }}" - # get all workflow runs for this PR gh api "/repos/${{ github.repository }}/actions/workflows/on_pr.yml/runs?event=pull_request&per_page=100" \ --jq ".workflow_runs[] | select(.pull_requests[]?.number == ${pr_number}) | select(.id < ${{ github.run_id }}) | {id: .id, sha: .head_sha, created: .created_at}" \ | jq -s 'sort_by(.created) | reverse | .[].id' -r \ | while read run_id; do echo "Checking run: $run_id" - # check if build-all job succeeded in this run run=$(gh api "/repos/${{ github.repository }}/actions/runs/${run_id}/jobs") echo "Run: $run" all_success=$(echo "$run" | jq '[.jobs[] | select(.name | contains("build-all")) | .conclusion] | all(. == "success")') echo "All success for $run_id: $all_success" if [ "$all_success" == "true" ]; then - # get the SHA for this run successful_sha=$(gh api "/repos/${{ github.repository }}/actions/runs/${run_id}" --jq '.head_sha') echo "last-successful-build-sha=${successful_sha}" >> $GITHUB_OUTPUT echo "Found last successful build at SHA: $successful_sha (run: $run_id)" @@ -97,10 +95,12 @@ jobs: needs: - path-filter - skip_build_sha - uses: ./.github/workflows/ecrbuild-all.yml + permissions: + contents: read + packages: write + uses: ./.github/workflows/ghcr-build-all.yml secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} e2e: if: (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize') && always() && (needs.build-all.result == 'success' || needs.build-all.result == 'skipped') @@ -111,55 +111,59 @@ jobs: uses: ./.github/workflows/e2e.yml with: image-tag-override: ${{ needs.skip_build_sha.outputs.last-successful-build-sha || '' }} - secrets: - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} deploy-preview: - if: needs.build-all.result == 'success' - uses: ./.github/workflows/pull-preview.yml + if: >- + always() && + contains(github.event.pull_request.labels.*.name, 'preview') && + github.event.action != 'closed' && + github.event.action != 'unlabeled' && + needs.skip_build_sha.result == 'success' && + (needs.build-all.result == 'success' || + (needs.build-all.result == 'skipped' && needs.skip_build_sha.outputs.last-successful-build-sha != '')) + uses: ./.github/workflows/deploy-stack.yml needs: + - skip_build_sha - build-all permissions: contents: read - deployments: write pull-requests: write - statuses: write with: - # PLATFORM_IMAGE: 246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-core:2b9a81a279c4e405bbedcdbb697c897ded52fbc0 - # JOBS_IMAGE: 246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-jobs:c786662f4899de16a621e366a485eca5adda4d6a - # MIGRATIONS_IMAGE: 246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7:c786662f4899de16a621e366a485eca5adda4d6a - PLATFORM_IMAGE: ${{ needs.build-all.outputs.core-image }} - JOBS_IMAGE: ${{ needs.build-all.outputs.jobs-image }} - MIGRATIONS_IMAGE: ${{ needs.build-all.outputs.base-image }} - AWS_REGION: "us-east-1" - COMPOSE_FILES: docker-compose.preview.pr.yml + action: deploy + image_tag: ${{ needs.skip_build_sha.outputs.last-successful-build-sha || github.event.pull_request.head.sha }} + stack_name: preview-pr-${{ github.event.pull_request.number }} + hostname: pr-${{ github.event.pull_request.number }}.pubstar.org + env_file: .env.preview.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GH_PAT_PR_PREVIEW_CLEANUP: ${{ secrets.GH_PAT_PR_PREVIEW_CLEANUP }} - PREVIEW_DATACITE_REPOSITORY_ID: ${{ secrets.PREVIEW_DATACITE_REPOSITORY_ID }} - PREVIEW_DATACITE_PASSWORD: ${{ secrets.PREVIEW_DATACITE_PASSWORD }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} close-preview: - uses: ./.github/workflows/pull-preview.yml - if: ${{(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview')) || (github.event.action == 'unlabeled' && github.event.label.name == 'preview')}} + if: (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview')) || (github.event.action == 'unlabeled' && github.event.label.name == 'preview') + uses: ./.github/workflows/deploy-stack.yml permissions: contents: read - deployments: write pull-requests: write - statuses: write with: - PLATFORM_IMAGE: "x" # not used - JOBS_IMAGE: "x" # not used - MIGRATIONS_IMAGE: "x" # not used - AWS_REGION: "us-east-1" + action: teardown + stack_name: preview-pr-${{ github.event.pull_request.number }} + hostname: pr-${{ github.event.pull_request.number }}.pubstar.org + env_file: .env.preview.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GH_PAT_PR_PREVIEW_CLEANUP: ${{ secrets.GH_PAT_PR_PREVIEW_CLEANUP }} - PREVIEW_DATACITE_REPOSITORY_ID: ${{ secrets.PREVIEW_DATACITE_REPOSITORY_ID }} - PREVIEW_DATACITE_PASSWORD: ${{ secrets.PREVIEW_DATACITE_PASSWORD }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} deploy-docs-preview: permissions: @@ -183,7 +187,7 @@ jobs: if: github.event.action == 'closed' && needs.path-filter.outputs.docs == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Close docs preview uses: rossjrw/pr-preview-action@v1 diff --git a/.github/workflows/on_tag.yml b/.github/workflows/on_tag.yml new file mode 100644 index 0000000000..9df13babf0 --- /dev/null +++ b/.github/workflows/on_tag.yml @@ -0,0 +1,94 @@ +name: Release & deploy + +on: + push: + tags: + - "v[0-9]*.[0-9]*.[0-9]*" + +permissions: + id-token: write + contents: write + packages: write + pull-requests: write + +concurrency: + group: deploy-prod + cancel-in-progress: false + +jobs: + validate-tag: + runs-on: ubuntu-latest + outputs: + prerelease: ${{ steps.meta.outputs.prerelease }} + steps: + - name: Validate semver and detect pre-release + id: meta + run: | + tag="${{ github.ref_name }}" + + if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc|experimental)\.[0-9]+)?$ ]]; then + echo "::error::Tag '$tag' does not match semver format (vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-{alpha,beta,rc,experimental}.N)" + exit 1 + fi + + if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "prerelease=false" >> $GITHUB_OUTPUT + else + echo "prerelease=true" >> $GITHUB_OUTPUT + fi + + build-all: + needs: validate-tag + permissions: + contents: read + packages: write + uses: ./.github/workflows/ghcr-build-all.yml + with: + image_tag: ${{ github.ref_name }} + publish_latest: ${{ needs.validate-tag.outputs.prerelease == 'false' }} + secrets: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + create-release: + needs: validate-tag + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + flags="--generate-notes" + + if [[ "${{ needs.validate-tag.outputs.prerelease }}" == "true" ]]; then + flags="$flags --prerelease" + fi + + gh release create "${{ github.ref_name }}" $flags + + deploy-prod: + needs: + - validate-tag + - build-all + if: needs.validate-tag.outputs.prerelease == 'false' + permissions: + contents: read + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: ${{ github.ref_name }} + stack_name: pubstar + hostname: ${{ vars.PROD_HOSTNAME }} + env_file: .env.enc + stack_file: stack.yml + uses_gateway: false + ssh_host_secret: SSH_HOST_PROD + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PROD }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} diff --git a/.github/workflows/pull-preview-script.sh b/.github/workflows/pull-preview-script.sh deleted file mode 100644 index 6af5557d58..0000000000 --- a/.github/workflows/pull-preview-script.sh +++ /dev/null @@ -1,7 +0,0 @@ -# install latest version of docker compose, by default it's using an ancient version -sudo curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-"$(uname -m)" \ - -o "$(which docker-compose)" && sudo chmod +x "$(which docker-compose)" - -docker image prune -a -f - -df -h diff --git a/.github/workflows/pull-preview.yml b/.github/workflows/pull-preview.yml deleted file mode 100644 index 7050e5f3e7..0000000000 --- a/.github/workflows/pull-preview.yml +++ /dev/null @@ -1,88 +0,0 @@ -on: - workflow_call: - inputs: - PLATFORM_IMAGE: - required: true - type: string - JOBS_IMAGE: - required: true - type: string - MIGRATIONS_IMAGE: - required: true - type: string - AWS_REGION: - required: true - type: string - ALWAYS_ON: - required: false - type: string - COMPOSE_FILES: - required: false - type: string - secrets: - AWS_ACCESS_KEY_ID: - required: true - AWS_SECRET_ACCESS_KEY: - required: true - GH_PAT_PR_PREVIEW_CLEANUP: - required: true - PREVIEW_DATACITE_REPOSITORY_ID: - required: true - PREVIEW_DATACITE_PASSWORD: - required: true - -permissions: - contents: read - deployments: write - pull-requests: write - statuses: write - -jobs: - preview: - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Copy .env file - run: cp ./self-host/.env.example ./self-host/.env - - - name: Configure pullpreview - env: - PLATFORM_IMAGE: ${{ inputs.PLATFORM_IMAGE }} - JOBS_IMAGE: ${{ inputs.JOBS_IMAGE }} - MIGRATIONS_IMAGE: ${{ inputs.MIGRATIONS_IMAGE }} - run: | - sed -i "s|image: PLATFORM_IMAGE|image: $PLATFORM_IMAGE|" docker-compose.preview.yml - sed -i "s|image: JOBS_IMAGE|image: $JOBS_IMAGE|" docker-compose.preview.yml - sed -i "s|image: MIGRATIONS_IMAGE|image: $MIGRATIONS_IMAGE|" docker-compose.preview.yml - sed -i "s|DATACITE_REPOSITORY_ID: DATACITE_REPOSITORY_ID|DATACITE_REPOSITORY_ID: ${{ secrets.PREVIEW_DATACITE_REPOSITORY_ID }}|" docker-compose.preview.yml - sed -i "s|DATACITE_PASSWORD: DATACITE_PASSWORD|DATACITE_PASSWORD: ${{ secrets.PREVIEW_DATACITE_PASSWORD }}|" docker-compose.preview.yml - sed -i "s|email someone@example.com|email dev@pubpub.org|" self-host/caddy/Caddyfile - sed -i "s|example.com|{\$PUBLIC_URL}|" self-host/caddy/Caddyfile - - - name: Get ECR token - id: ecrtoken - run: echo "value=$(aws ecr get-login-password --region us-east-1)" >> $GITHUB_OUTPUT - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: "us-east-1" - - - uses: pullpreview/action@v5 - with: - label: preview - admins: 3mcd - compose_files: ./self-host/docker-compose.yml,docker-compose.preview.yml,${{ inputs.COMPOSE_FILES }} - default_port: 443 - instance_type: small - always_on: ${{ inputs.ALWAYS_ON }} - ports: 80,443 - registries: docker://AWS:${{steps.ecrtoken.outputs.value}}@246372085946.dkr.ecr.us-east-1.amazonaws.com - github_token: ${{ secrets.GH_PAT_PR_PREVIEW_CLEANUP }} - pre_script: "./.github/workflows/pull-preview-script.sh" - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ inputs.AWS_REGION }} - PULLPREVIEW_LOGGER_LEVEL: DEBUG diff --git a/.github/workflows/reset-preview-envs.yml b/.github/workflows/reset-preview-envs.yml new file mode 100644 index 0000000000..a3d8348cb4 --- /dev/null +++ b/.github/workflows/reset-preview-envs.yml @@ -0,0 +1,88 @@ +# this workflow redeploys the sandbox and preview environments every 6 hours +# to ensure that the environments are always in a clean state +name: Reset preview environments + +on: + schedule: + # every 6 hours + - cron: '0 */6 * * *' + workflow_dispatch: + +permissions: + id-token: write + contents: read + packages: write + pull-requests: write + +concurrency: + group: reset-preview-envs + cancel-in-progress: false + +jobs: + reset-sandbox: + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: latest + stack_name: sandbox + hostname: sandbox.pubstar.org + env_file: .env.sandbox.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + + find-active-previews: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.find.outputs.matrix }} + has_previews: ${{ steps.find.outputs.has_previews }} + steps: + - uses: actions/checkout@v6 + + - name: Find open PRs with preview label + id: find + env: + GH_TOKEN: ${{ github.token }} + run: | + previews=$(gh pr list --label preview --state open --json number,headRefOid --jq '[.[] | {number: .number, sha: .headRefOid}]') + count=$(echo "$previews" | jq length) + + echo "found $count active preview(s)" + + if [ "$count" -eq 0 ]; then + echo "has_previews=false" >> "$GITHUB_OUTPUT" + echo "matrix={\"include\":[]}" >> "$GITHUB_OUTPUT" + else + echo "has_previews=true" >> "$GITHUB_OUTPUT" + matrix=$(echo "$previews" | jq -c '{include: [.[] | {pr_number: .number, sha: .headRefOid}]}') + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + fi + + reset-previews: + needs: find-active-previews + if: needs.find-active-previews.outputs.has_previews == 'true' + strategy: + matrix: ${{ fromJson(needs.find-active-previews.outputs.matrix) }} + fail-fast: false + uses: ./.github/workflows/deploy-stack.yml + with: + action: deploy + image_tag: ${{ matrix.sha }} + stack_name: preview-pr-${{ matrix.pr_number }} + hostname: pr-${{ matrix.pr_number }}.pubstar.org + env_file: .env.preview.enc + stack_file: stack.preview.yml + uses_gateway: true + ssh_host_secret: SSH_HOST_PREVIEW + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USER: ${{ secrets.SSH_USER }} + SSH_HOST: ${{ secrets.SSH_HOST_PREVIEW }} + GHCR_USER: ${{ secrets.GHCR_USER }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} diff --git a/.gitignore b/.gitignore index 38a05eafeb..067acc7fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -73,5 +73,12 @@ storybook-static .local_data +# infra decrypted env files (encrypted versions are tracked) +infra/.env +infra/.env.staging + +!.env*.enc +!.env.example + # mock-notify store .notifications.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 1495392b2e..58fdc527d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,7 +41,7 @@ } }, "[json]": { - "editor.defaultFormatter": "vscode.json-language-features", + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { "source.biome": "always", "source.fixAll.biome": "always" diff --git a/Caddyfile.test b/Caddyfile.test index 6f645d8b26..e46c20c054 100644 --- a/Caddyfile.test +++ b/Caddyfile.test @@ -24,7 +24,7 @@ example.com { # if you want to use a different domain for your files, you can do so here # for instance, now all your files will be accessible at assets.example.com -# if you go this route, be sure to update your ASSETS_STORAGE_ENDPOINT in .env and restart your services +# if you go this route, be sure to update your S3_ENDPOINT in .env and restart your services # assets.example.com { # reverse_proxy minio:9000 # } diff --git a/Dockerfile b/Dockerfile index 9b052300e4..b624c98a39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,8 @@ # If you need more help, visit the Dockerfile reference guide at # https://docs.docker.com/go/dockerfile-reference/ -ARG NODE_VERSION=22.13.1 -ARG ALPINE_VERSION=3.20 +ARG NODE_VERSION=24.15.0 +ARG ALPINE_VERSION=3.22 ARG PACKAGE ARG PORT=3000 @@ -23,7 +23,7 @@ ARG PNPM_VERSION # Instll dependencies we need at the end -RUN apk add ca-certificates curl postgresql +RUN apk add ca-certificates curl postgresql17 # Setup RDS CA Certificates RUN curl -L \ @@ -78,6 +78,9 @@ ENV DOCKERBUILD=1 ARG CI ENV CI=$CI +ARG BASE_PATH="" +ENV BASE_PATH=$BASE_PATH + RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN,env=SENTRY_AUTH_TOKEN \ pnpm --filter $PACKAGE build @@ -97,7 +100,7 @@ WORKDIR /usr/src/app COPY --from=prepare-jobs --chown=node:node /tmp/app . # If the package is site-builder, create necessary directories and set permissions -RUN if [ "$PACKAGE" = "site-builder" ]; then \ +RUN if [ "$PACKAGE" = "site-builder-2" ]; then \ mkdir -p /usr/src/app/builds /usr/src/app/.astro /usr/src/app/dist && \ chown -R node:node /usr/src/app/builds /usr/src/app/.astro /usr/src/app/dist; \ fi @@ -134,7 +137,16 @@ WORKDIR /usr/src/app COPY --from=withpackage --chown=node:node /usr/src/app/core/.next/standalone ./ COPY --from=withpackage --chown=node:node /usr/src/app/core/.next/static ./core/.next/static COPY --from=withpackage --chown=node:node /usr/src/app/core/public ./core/public -# needed to set the database url correctly based on PGHOST variables -COPY --from=withpackage --chown=node:node /usr/src/app/core/.env.docker ./core/.env +# migration sql files, applied automatically during startup instrumentation +COPY --from=withpackage --chown=node:node /usr/src/app/core/prisma/migrations ./core/prisma/migrations + +CMD ["node", "--enable-source-maps", "core/server.js"] + +### Mock Notify + +FROM prod-setup AS next-app-mock-notify +WORKDIR /usr/src/app +COPY --from=withpackage --chown=node:node /usr/src/app/mock-notify/.next/standalone ./ +COPY --from=withpackage --chown=node:node /usr/src/app/mock-notify/.next/static ./mock-notify/.next/static -CMD ["node", "core/server.js"] +CMD ["node", "mock-notify/server.js"] diff --git a/Dockerfile.caddy b/Dockerfile.caddy deleted file mode 100644 index 388120b5ce..0000000000 --- a/Dockerfile.caddy +++ /dev/null @@ -1,9 +0,0 @@ -FROM caddy:builder-alpine AS builder - -RUN xcaddy build --with github.com/sagikazarmark/caddy-fs-s3 - -FROM caddy:alpine - -COPY --from=builder /usr/bin/caddy /usr/bin/caddy - -CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"] \ No newline at end of file diff --git a/biome.jsonc b/biome.jsonc index 46f2139ffc..eab97c8912 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -162,14 +162,14 @@ "!lib/**", "!db/**", "!ui/**", - "!@pubpub/**", + "!@pubstar/**", "!utils/**", "!contracts/**", "!logger/**", "!lib", "!db", "!ui", - "!@pubpub", + "!@pubstar", "!utils", "!contracts", "!logger" @@ -182,14 +182,14 @@ "lib/**", "db/**", "ui/**", - "@pubpub/**", + "@pubstar/**", "utils/**", "contracts/**", "logger/**", "lib", "db", "ui", - "@pubpub", + "@pubstar", "utils", "contracts", "logger" @@ -218,14 +218,14 @@ "!lib/**", "!db/**", "!ui/**", - "!@pubpub/**", + "!@pubstar/**", "!utils/**", "!contracts/**", "!logger/**", "!lib", "!db", "!ui", - "!@pubpub", + "!@pubstar", "!utils", "!contracts", "!logger" @@ -238,14 +238,14 @@ "lib/**", "db/**", "ui/**", - "@pubpub/**", + "@pubstar/**", "utils/**", "contracts/**", "logger/**", "lib", "db", "ui", - "@pubpub", + "@pubstar", "utils", "contracts", "logger" diff --git a/config/tailwind/package.json b/config/tailwind/package.json index 38f4500628..644ae2f902 100644 --- a/config/tailwind/package.json +++ b/config/tailwind/package.json @@ -1,5 +1,5 @@ { - "name": "@pubpub/tailwind", + "name": "@pubstar/tailwind", "type": "module", "version": "0.0.0", "private": true, diff --git a/core/.env.development b/core/.env.development index c618b871f4..0ea880b296 100644 --- a/core/.env.development +++ b/core/.env.development @@ -1,20 +1,20 @@ -MAILGUN_SMTP_HOST=localhost -MAILGUN_SMTP_PORT=54325 +SMTP_HOST=localhost +SMTP_PORT=54325 API_KEY=super_secret_key DATABASE_URL=postgresql://postgres:postgres@localhost:54322/postgres -PUBPUB_URL=http://localhost:3000 -ASSETS_BUCKET_NAME=assets.v7.pubpub.org -ASSETS_REGION=us-east-1 +PUBSTAR_URL=http://localhost:3000 # mninio defaults -ASSETS_UPLOAD_KEY=pubpubuser -ASSETS_UPLOAD_SECRET_KEY=pubpubpass -ASSETS_STORAGE_ENDPOINT=http://localhost:9000 +S3_BUCKET_NAME=assets.pubpub.org +S3_REGION=us-east-1 +S3_ACCESS_KEY=pubpubuser +S3_SECRET_KEY=pubpubpass +S3_STORAGE_ENDPOINT=http://localhost:9000 -MAILGUN_SMTP_PASSWORD=xxx -MAILGUN_SMTP_USERNAME=xxx +SMTP_PASSWORD=xxx +SMTP_USERNAME=xxx -OTEL_SERVICE_NAME=pubpub-v7-dev +OTEL_SERVICE_NAME=pubstar-v7-dev HONEYCOMB_API_KEY=xxx ARTILLERY_CLOUD_API_KEY=xxx @@ -29,3 +29,8 @@ GCLOUD_KEY_FILE=xxx SITE_BUILDER_ENDPOINT=http://localhost:4000 VALKEY_HOST='localhost' + +FLAGS=show-test-only-tools:on + +SMTP_FROM=dev@pubstar.org +SMTP_FROM_NAME=Pubstar Team \ No newline at end of file diff --git a/core/.env.docker b/core/.env.docker deleted file mode 100644 index d7f38fe8ce..0000000000 --- a/core/.env.docker +++ /dev/null @@ -1 +0,0 @@ -DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE} diff --git a/core/.env.template b/core/.env.template deleted file mode 100644 index 264b00e44b..0000000000 --- a/core/.env.template +++ /dev/null @@ -1,2 +0,0 @@ -OTEL_SERVICE_NAME="pubpub-v7-dev" # should be shared across components but not environments -HONEYCOMB_API_KEY="" diff --git a/core/.github/workflows/playwright.yml b/core/.github/workflows/playwright.yml deleted file mode 100644 index b050f147ee..0000000000 --- a/core/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22.13.1 - - name: Install dependencies - run: npm install -g pnpm && pnpm install - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - - name: Run Playwright tests - run: pnpm exec playwright test - - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/core/.storybook/__mocks__/env.mock.ts b/core/.storybook/__mocks__/env.mock.ts index f5b9afb630..01e192ff17 100644 --- a/core/.storybook/__mocks__/env.mock.ts +++ b/core/.storybook/__mocks__/env.mock.ts @@ -1,5 +1,5 @@ import type { env as envOriginal } from "~/lib/env/env" export const env = { - PUBPUB_URL: "http://localhost:6006", + PUBSTAR_URL: "http://localhost:6006", } satisfies Partial diff --git a/core/.storybook/main.ts b/core/.storybook/main.ts index 5bdbdbde2a..5d0c03ff61 100644 --- a/core/.storybook/main.ts +++ b/core/.storybook/main.ts @@ -31,7 +31,7 @@ const config: StorybookConfig = { ...env, // these options are not propogated to the client i think SKIP_VALIDATION: "true", - PUBPUB_URL: "http://localhost:6006", + PUBSTAR_URL: "http://localhost:6006", } }, // this causes and error: `expected expression, got reserved word 'enum'` diff --git a/core/actions/_lib/ActionConfigBuilder.ts b/core/actions/_lib/ActionConfigBuilder.ts index a4a6409f6e..825c9ea8e6 100644 --- a/core/actions/_lib/ActionConfigBuilder.ts +++ b/core/actions/_lib/ActionConfigBuilder.ts @@ -336,7 +336,7 @@ export class ActionConfigBuilder< try { // to prevent this from being bundled into the main bundle, we import it here - const { interpolate } = await import("@pubpub/json-interpolate") + const { interpolate } = await import("@pubstar/json-interpolate") const interpolateValue = async (value: unknown, data: unknown): Promise => { if (typeof value !== "string") { diff --git a/core/actions/_lib/ActionFieldJsonataTestPanel.tsx b/core/actions/_lib/ActionFieldJsonataTestPanel.tsx index 36fda040a3..1b4d5eeb23 100644 --- a/core/actions/_lib/ActionFieldJsonataTestPanel.tsx +++ b/core/actions/_lib/ActionFieldJsonataTestPanel.tsx @@ -8,7 +8,7 @@ import { skipToken } from "@tanstack/react-query" import { AlertCircle, CheckCircle2, Loader2, Play, Zap, ZapOff } from "lucide-react" import { useWatch } from "react-hook-form" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { Alert, AlertDescription } from "ui/alert" import { Button } from "ui/button" import { Label } from "ui/label" @@ -147,7 +147,7 @@ export function ActionFieldJsonataTestPanel(props: { }, stage, env: { - PUBPUB_URL: typeof window !== "undefined" ? window.location.origin : "", + PUBSTAR_URL: typeof window !== "undefined" ? window.location.origin : "", }, useDummyValues: true, ...pubOrJson, diff --git a/core/actions/_lib/evaluateConditions.ts b/core/actions/_lib/evaluateConditions.ts index 863bb0c5ed..3377a9d124 100644 --- a/core/actions/_lib/evaluateConditions.ts +++ b/core/actions/_lib/evaluateConditions.ts @@ -1,6 +1,6 @@ import type { ConditionBlock } from "db/types" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { AutomationConditionBlockType } from "db/public" type ConditionItem = ConditionBlock["items"][number] diff --git a/core/actions/_lib/interpolationContext.ts b/core/actions/_lib/interpolationContext.ts index ff7fff466d..b70041a29a 100644 --- a/core/actions/_lib/interpolationContext.ts +++ b/core/actions/_lib/interpolationContext.ts @@ -44,7 +44,7 @@ type InterpolationAction = Pick type InterpolationUser = Pick type InterpolationEnv = { - PUBPUB_URL: string + PUBSTAR_URL: string } type BuildInterpolationContextArgsBase = { @@ -95,7 +95,7 @@ export function buildInterpolationContext( ): InterpolationContext { const baseContext: Omit = { env: { - PUBPUB_URL: args.env.PUBPUB_URL, + PUBSTAR_URL: args.env.PUBSTAR_URL, }, community: { id: args.community.id, diff --git a/core/actions/_lib/resolveAutomationInput.ts b/core/actions/_lib/resolveAutomationInput.ts index 28649470e7..eec6c1ab0c 100644 --- a/core/actions/_lib/resolveAutomationInput.ts +++ b/core/actions/_lib/resolveAutomationInput.ts @@ -5,7 +5,7 @@ import type { InterpolationContext } from "./interpolationContext" import jsonata from "jsonata" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { logger } from "logger" import { tryCatch } from "utils/try-catch" diff --git a/core/actions/_lib/runActionInstance.db.test.ts b/core/actions/_lib/runActionInstance.db.test.ts index af61396694..f03643383b 100644 --- a/core/actions/_lib/runActionInstance.db.test.ts +++ b/core/actions/_lib/runActionInstance.db.test.ts @@ -61,7 +61,7 @@ const pubTriggerTestSeed = async () => { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", body: "Hello", subject: "Test", }, diff --git a/core/actions/_lib/runAutomation.ts b/core/actions/_lib/runAutomation.ts index a25e4c479f..7709c702ab 100644 --- a/core/actions/_lib/runAutomation.ts +++ b/core/actions/_lib/runAutomation.ts @@ -210,7 +210,7 @@ async function evaluateAutomationConditions(args: { const input = buildInterpolationContext({ useDummyValues: false, env: { - PUBPUB_URL: env.PUBPUB_URL, + PUBSTAR_URL: env.PUBSTAR_URL, }, community: args.community, stage, @@ -411,7 +411,7 @@ const runActionInstance = async (args: RunActionInstanceArgs): Promise & { manualActionInstancesOverrideArgs: { [actionInstanceId: ActionInstancesId]: Record @@ -85,6 +86,8 @@ export const runAutomationManual = defineServerAction(async function runActionIn }) if (!result.success) { + logger.error({ msg: "Automation run failed", result }) + return { success: false, error: result.actionRuns diff --git a/core/actions/buildSite/formActions.ts b/core/actions/buildSite/formActions.ts index 744ca6da15..e3fff8adaf 100644 --- a/core/actions/buildSite/formActions.ts +++ b/core/actions/buildSite/formActions.ts @@ -1,6 +1,6 @@ "use server" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { logger } from "logger" import { getLoginData } from "~/lib/authentication/loginData" @@ -59,7 +59,7 @@ export const previewResult = defineServerAction(async function previewResult({ const pubContext = buildInterpolationContext({ community, pub, - env: { PUBPUB_URL: env.PUBPUB_URL }, + env: { PUBSTAR_URL: env.PUBSTAR_URL }, useDummyValues: true, }) const _interpolatedSlug = await interpolate(slug, pubContext) diff --git a/core/actions/buildSite/run.tsx b/core/actions/buildSite/run.tsx index df430803a2..28799f0040 100644 --- a/core/actions/buildSite/run.tsx +++ b/core/actions/buildSite/run.tsx @@ -9,7 +9,7 @@ import type { action } from "./action" import { initClient } from "@ts-rest/core" import { JSONPath } from "jsonpath-plus" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { siteBuilderApi } from "contracts/resources/site-builder-2" import { logger } from "logger" import { tryCatch } from "utils/try-catch" @@ -124,7 +124,7 @@ export const run = defineRun( communityId, communityName: community.name, subpath: config.subpath, - siteUrl: env.PUBPUB_URL, + siteUrl: env.PUBSTAR_URL, pageGroups, }, headers: { diff --git a/core/actions/createPub/run.ts b/core/actions/createPub/run.ts index fb18d2746a..101e0bc4e0 100644 --- a/core/actions/createPub/run.ts +++ b/core/actions/createPub/run.ts @@ -4,7 +4,7 @@ import type { Json, JsonValue } from "contracts" import type { PubFieldsId, PubsId, PubTypesId, StagesId } from "db/public" import type { action } from "./action" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { logger } from "logger" import { db } from "~/kysely/database" diff --git a/core/actions/http/run.tsx b/core/actions/http/run.tsx index a46d0b612e..44b0766928 100644 --- a/core/actions/http/run.tsx +++ b/core/actions/http/run.tsx @@ -7,9 +7,10 @@ import type { action } from "./action" import { JSONPath } from "jsonpath-plus" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { logger } from "logger" +import { env } from "~/lib/env/env" import { updatePub } from "~/lib/server/pub" import { defineRun } from "../types" @@ -31,11 +32,49 @@ const extractValue = async (data: unknown, expression: string): Promise return interpolate(expression, data) } +const isAllowedHttpHost = (hostname: string, allowedDomains: string[]) => { + const normalizedHostname = hostname.toLowerCase() + + return allowedDomains.some((allowedDomain) => { + const normalizedDomain = allowedDomain.toLowerCase() + const isSubdomainRule = normalizedDomain.startsWith(".") + + if (!isSubdomainRule) { + return normalizedHostname === normalizedDomain + } + + const domain = normalizedDomain.slice(1) + return normalizedHostname === domain || normalizedHostname.endsWith(normalizedDomain) + }) +} + export const run = defineRun(async ({ pub, config, lastModifiedBy }) => { const { url, method, authToken } = config const finalOutputMap = config?.outputMap ?? [] + const parsedUrl = new URL(url) + const isHttpProtocol = parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" + + if (!isHttpProtocol) { + return { + success: false, + title: "Error", + error: `Invalid URL protocol "${parsedUrl.protocol}". Only http and https are allowed.`, + } + } + + const allowedDomains = env.FLAGS?.get("http-allowed-domains") ?? [] + const shouldRestrictDomains = allowedDomains.length > 0 + + if (shouldRestrictDomains && !isAllowedHttpHost(parsedUrl.hostname, allowedDomains)) { + return { + success: false, + title: "Error", + error: `Domain "${parsedUrl.hostname}" is not allowed. Allowed domains: ${allowedDomains.join(", ")}`, + } + } + const body = typeof config.body === "string" ? config.body : JSON.stringify(config.body) const res = await fetch(url, { diff --git a/core/app/(user)/communities/actions.ts b/core/app/(user)/communities/actions.ts index 87ebc930a1..7e53b544e8 100644 --- a/core/app/(user)/communities/actions.ts +++ b/core/app/(user)/communities/actions.ts @@ -11,6 +11,7 @@ import { db } from "~/kysely/database" import { isUniqueConstraintError } from "~/kysely/errors" import { getLoginData } from "~/lib/authentication/loginData" import { createSiteBuilderToken } from "~/lib/server/apiAccessTokens" +import { normalizeAssetUrl } from "~/lib/server/assets" import { autoRevalidate } from "~/lib/server/cache/autoRevalidate" import { defineServerAction } from "~/lib/server/defineServerAction" import { slugifyString } from "~/lib/string" @@ -45,6 +46,9 @@ export const createCommunity = defineServerAction(async function createCommunity error: "Cannot update example community", } } + + const normalizedAvatar = avatar ? normalizeAssetUrl(avatar) : null + try { const { communityId } = await autoRevalidate( db @@ -54,7 +58,7 @@ export const createCommunity = defineServerAction(async function createCommunity .values({ name, slug: slugifyString(slug), - avatar, + avatar: normalizedAvatar, }) .returning("id") ) diff --git a/core/app/(user)/communities/page.tsx b/core/app/(user)/communities/page.tsx index aa145a5238..c31d4e2b54 100644 --- a/core/app/(user)/communities/page.tsx +++ b/core/app/(user)/communities/page.tsx @@ -1,51 +1,9 @@ -import type { TableCommunity } from "./getCommunityTableColumns" - -import { db } from "~/kysely/database" -import { getPageLoginData } from "~/lib/authentication/loginData" -import { AddCommunity } from "./AddCommunityDialog" -import { CommunityTable } from "./CommunityTable" +import { redirect } from "next/navigation" export const metadata = { title: "Communities", } -export default async function Page() { - const { user } = await getPageLoginData() - - if (!user.isSuperAdmin) { - return null - } - - const communities = await db - .selectFrom("communities") - .select([ - "communities.id", - "communities.name", - "communities.slug", - "communities.avatar", - "createdAt", - ]) - .execute() - - const tableMembers = communities.map((community) => { - const { id, name, slug, avatar, createdAt } = community - return { - id, - name, - slug, - avatar, - created: new Date(createdAt), - } satisfies TableCommunity - }) - return ( - <> -
-

Communities

- -
-
- -
- - ) +export default function Page() { + redirect("/superadmin") } diff --git a/core/app/(user)/settings/AvatarEditor.tsx b/core/app/(user)/settings/AvatarEditor.tsx index bb60501a8f..8c4a460995 100644 --- a/core/app/(user)/settings/AvatarEditor.tsx +++ b/core/app/(user)/settings/AvatarEditor.tsx @@ -1,3 +1,5 @@ +import type { FileUploadProps } from "ui/customRenderers/fileUpload/fileUpload" + import { useState } from "react" import dynamic from "next/dynamic" import { ImagePlus, XIcon } from "lucide-react" @@ -28,7 +30,7 @@ export const AvatarEditor = ({ initials: string avatar: string | null onEdit: (avatar: string | null) => void - upload: (fileName: string) => Promise + upload: FileUploadProps["upload"] allowedFileTypes?: string[] showDeleteButton?: boolean label?: string diff --git a/core/app/(user)/settings/actions.ts b/core/app/(user)/settings/actions.ts index 0450f0b7bd..cfb3866d85 100644 --- a/core/app/(user)/settings/actions.ts +++ b/core/app/(user)/settings/actions.ts @@ -11,7 +11,12 @@ import { logger } from "logger" import { db } from "~/kysely/database" import { getLoginData } from "~/lib/authentication/loginData" import { env } from "~/lib/env/env" -import { ApiError, deleteFileFromS3, generateSignedUserAvatarUploadUrl } from "~/lib/server" +import { + ApiError, + deleteFileFromS3, + generateSignedUserAvatarUploadUrl, + normalizeAssetUrl, +} from "~/lib/server" import { defineServerAction } from "~/lib/server/defineServerAction" import { maybeWithTrx } from "~/lib/server/maybeWithTrx" import { updateUser } from "~/lib/server/user" @@ -91,11 +96,14 @@ export const updateUserAvatar = defineServerAction(async function updateUserAvat try { await maybeWithTrx(db, async (trx) => { const currentAvatar = user.avatar + + const normalizedAvatar = fileName ? normalizeAssetUrl(fileName) : null + if (fileName) { await updateUser( { id: userId, - avatar: fileName, + avatar: normalizedAvatar, }, trx ) @@ -111,7 +119,7 @@ export const updateUserAvatar = defineServerAction(async function updateUserAvat // make sure user cannot secretely delete any other image const doesAvatarContainUserId = currentAvatar?.includes(`/avatars/${userId}/`) - if (currentAvatar && currentAvatar !== fileName && doesAvatarContainUserId) { + if (currentAvatar && currentAvatar !== normalizedAvatar && doesAvatarContainUserId) { await deleteFileFromS3(currentAvatar) } }) diff --git a/core/app/(user)/superadmin/BackupsPanel.tsx b/core/app/(user)/superadmin/BackupsPanel.tsx new file mode 100644 index 0000000000..75b4bb7e3f --- /dev/null +++ b/core/app/(user)/superadmin/BackupsPanel.tsx @@ -0,0 +1,304 @@ +"use client" + +import type { ColumnDef } from "@tanstack/react-table" +import type { BackupRecordsId, BackupStatus } from "db/public" + +import { useMemo, useState, useTransition } from "react" +import { useRouter } from "next/navigation" +import { useForm } from "react-hook-form" + +import { Badge } from "ui/badge" +import { Button } from "ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "ui/card" +import { RefreshCw, XCircle } from "ui/icon" +import { Input } from "ui/input" +import { FormSubmitButton } from "ui/submit-button" +import { Switch } from "ui/switch" +import { toast } from "ui/use-toast" +import { cn } from "utils" + +import { DataTable } from "~/app/components/DataTable/v2/DataTable" +import { didSucceed, useServerAction } from "~/lib/serverActions" +import { deleteBackup, triggerBackup, updateBackupConfig } from "./backup-actions" + +type BackupConfig = { + enabled: boolean + intervalHours: number + retentionDays: number + notificationEmail: string | null +} + +export type BackupRow = { + id: string + filename: string + s3Key: string + sizeBytes: bigint | string | null + status: BackupStatus + error: string | null + startedAt: Date | string | null + completedAt: Date | string | null + createdAt: Date | string +} + +const formatDate = (value: Date | string | null) => { + if (!value) { + return "-" + } + + const date = typeof value === "string" ? new Date(value) : value + return date.toLocaleString() +} + +const formatSize = (value: BackupRow["sizeBytes"]) => { + if (!value) { + return "-" + } + + const bytes = typeof value === "bigint" ? Number(value) : Number(value) + if (!Number.isFinite(bytes)) { + return "-" + } + + const mb = bytes / 1024 / 1024 + return `${mb.toFixed(1)} MB` +} + +const StatusBadge = ({ status }: { status: BackupStatus }) => { + if (status === "completed") { + return {status} + } + + if (status === "in_progress") { + return {status} + } + + if (status === "pending") { + return {status} + } + + return {status} +} + +export const BackupsPanel = ({ + backups, + config, +}: { + backups: BackupRow[] + config: BackupConfig +}) => { + const router = useRouter() + const triggerBackupAction = useServerAction(triggerBackup) + const [isPending, startTransition] = useTransition() + const [enabled, setEnabled] = useState(config.enabled) + const [_intervalHours, _setIntervalHours] = useState(String(config.intervalHours)) + const [_retentionDays, _setRetentionDays] = useState(String(config.retentionDays)) + + const columns = useMemo( + () => + [ + { + id: "filename", + header: "Filename", + cell: ({ row }) => ( +
+ {row.original.filename} +
+ ), + }, + { + id: "status", + header: "Status", + cell: ({ row }) => , + }, + { + id: "sizeBytes", + header: "Size", + accessorFn: (row) => formatSize(row.sizeBytes), + }, + { + id: "startedAt", + header: "Started", + accessorFn: (row) => formatDate(row.startedAt), + }, + { + id: "completedAt", + header: "Completed", + accessorFn: (row) => formatDate(row.completedAt), + }, + { + id: "actions", + header: "", + cell: ({ row }) => ( + + ), + }, + ] as const satisfies ColumnDef[], + [isPending, router] + ) + const backupConfigAction = useServerAction(updateBackupConfig) + + const handleSave = async (data: { + intervalHours: number + retentionDays: number + notificationEmail: string + }) => { + const result = await backupConfigAction({ + enabled, + intervalHours: data.intervalHours, + retentionDays: data.retentionDays, + notificationEmail: data.notificationEmail.trim() || null, + }) + + if (didSucceed(result)) { + toast.success("Backup configuration saved successfully") + } + + router.refresh() + } + + const handleCreateNow = async () => { + const result = await triggerBackupAction() + if (didSucceed(result)) { + toast.success("Backup created successfully") + } + + router.refresh() + } + + const hasFailedBackup = backups.some((backup) => backup.status === "failed") + + const backupIntervalForm = useForm<{ + intervalHours: number + retentionDays: number + notificationEmail: string + }>({ + defaultValues: { + intervalHours: config.intervalHours, + retentionDays: config.retentionDays, + notificationEmail: config.notificationEmail ?? "", + }, + }) + + return ( +
+
+

Backups

+ +
+ + + + Backup configuration + + Configure backup schedule and retention. Backup storage credentials are read + from environment variables. + + + + +
+ + Enable scheduled backups +
+ +
+
+
+

Interval (hours)

+ +
+ +
+

Retention (days)

+ +
+
+ +
+

Notification email

+ +

+ Receives an email when a backup fails. Leave empty to disable. +

+
+ + + +
+
+ + {hasFailedBackup && ( + + + Failed backups + + One or more backups failed. Check the error details below. + + + + )} + +
+ +
+ + {backups + .filter((backup) => backup.error) + .map((backup) => ( + + + + + {backup.filename} + + + +
+								{backup.error}
+							
+
+
+ ))} +
+ ) +} diff --git a/core/app/(user)/superadmin/MigrationsPanel.tsx b/core/app/(user)/superadmin/MigrationsPanel.tsx new file mode 100644 index 0000000000..a2d9713bc4 --- /dev/null +++ b/core/app/(user)/superadmin/MigrationsPanel.tsx @@ -0,0 +1,362 @@ +"use client" + +import type { ColumnDef } from "@tanstack/react-table" + +import { useMemo, useState, useTransition } from "react" +import { useRouter } from "next/navigation" + +import { Badge } from "ui/badge" +import { Button } from "ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "ui/card" +import { CircleCheck, RefreshCw, XCircle } from "ui/icon" +import { cn } from "utils" + +import { DataTable } from "~/app/components/DataTable/v2/DataTable" +import { resolveMigration, retryMigrations } from "./actions" + +export type MigrationRow = { + id: string + checksum: string + finished_at: Date | string | null + migration_name: string + logs: string | null + rolled_back_at: Date | string | null + started_at: Date | string + applied_steps_count: number +} + +type MigrationStatus = "applied" | "failed" | "rolled_back" + +function getMigrationStatus(row: MigrationRow): MigrationStatus { + if (row.rolled_back_at) { + return "rolled_back" + } + + if (row.finished_at) { + return "applied" + } + + return "failed" +} + +function StatusBadge({ status }: { status: MigrationStatus }) { + if (status === "applied") { + return ( + + + applied + + ) + } + + if (status === "rolled_back") { + return ( + + + rolled back + + ) + } + + return ( + + + failed + + ) +} + +function _formatDate(d: Date | string | null): string { + if (!d) { + return "-" + } + + const date = typeof d === "string" ? new Date(d) : d + return date.toLocaleString() +} + +function _extractMigrationLabel(name: string): string { + const parts = name.split("_") + + if (parts.length <= 1) { + return name + } + + return parts.slice(1).join("_") +} + +export const MigrationsPanel = ({ + migrations, + error, +}: { + migrations: MigrationRow[] + error?: string +}) => { + const router = useRouter() + const [isPending, startTransition] = useTransition() + const [expandedRow, setExpandedRow] = useState(null) + + const failedMigrations = migrations.filter((m) => getMigrationStatus(m) === "failed") + const appliedCount = migrations.filter((m) => getMigrationStatus(m) === "applied").length + const rolledBackCount = migrations.filter((m) => getMigrationStatus(m) === "rolled_back").length + + const handleResolve = (migrationName: string) => { + startTransition(async () => { + await resolveMigration({ migrationName }) + router.refresh() + }) + } + + const handleRetry = () => { + startTransition(async () => { + await retryMigrations() + router.refresh() + }) + } + + const columns = useMemo( + () => + [ + { + id: "migration", + header: "Migration", + cell: ({ row }) => ( + <> +
+ + {_extractMigrationLabel(row.original.migration_name)} + + + + {row.original.migration_name.split("_")[0]} + +
+ + {expandedRow === row.original.id && row.original.logs && ( +
+									{row.original.logs}
+								
+ )} + + ), + }, + { + id: "status", + header: "Status", + size: 120, + cell: ({ row }) => , + }, + { + id: "applied_at", + header: "Applied at", + size: 180, + accessorFn: (row) => _formatDate(row.finished_at), + }, + ] as const satisfies ColumnDef[], + [expandedRow] + ) + + if (error) { + return ( + + + Migrations + Could not load migration status + + +

{error}

+
+
+ ) + } + + return ( +
+
+

Migrations

+ + +
+ +
+ + +
+ +
+
+

{appliedCount}

+

applied

+
+
+
+ + + +
+ +
+
+

{rolledBackCount}

+

rolled back

+
+
+
+ + + +
0 ? "bg-red-100" : "bg-muted" + )} + > + 0 + ? "text-red-700" + : "text-muted-foreground" + )} + /> +
+
+

{failedMigrations.length}

+

failed

+
+
+
+
+ + {failedMigrations.length > 0 && ( + + + Failed migrations + + These migrations did not complete. Since migrations run inside a + database transaction, the SQL changes were rolled back. You can mark + them as resolved and retry. + + + +
+ {failedMigrations.map((m) => ( +
+
+

+ {m.migration_name} +

+ + {m.logs && ( +

+ {m.logs.slice(0, 200)} +

+ )} +
+ + +
+ ))} +
+
+
+ )} + +
+ setExpandedRow(row.original.id)} + pagination={{ + pageIndex: 0, + pageSize: 200, + }} + /> + + {/* + + + Migration + Status + Applied at + + + + {[...migrations].reverse().map((m) => { + const status = getMigrationStatus(m) + const isExpanded = expandedRow === m.id + + return ( + setExpandedRow(isExpanded ? null : m.id)} + > + +
+ + {extractMigrationLabel(m.migration_name)} + + + + {m.migration_name.split("_")[0]} + +
+ + {isExpanded && m.logs && ( +
+												{m.logs}
+											
+ )} +
+ + + + + + + {formatDate(m.finished_at)} + +
+ ) + })} + + {migrations.length === 0 && ( + + + No migrations found + + + )} +
+
*/} +
+
+ ) +} diff --git a/core/app/(user)/superadmin/SuperadminDashboard.tsx b/core/app/(user)/superadmin/SuperadminDashboard.tsx new file mode 100644 index 0000000000..cd53352170 --- /dev/null +++ b/core/app/(user)/superadmin/SuperadminDashboard.tsx @@ -0,0 +1,66 @@ +"use client" + +import type { TableCommunity } from "../communities/getCommunityTableColumns" +import type { BackupRow } from "./BackupsPanel" +import type { MigrationRow } from "./MigrationsPanel" + +import { parseAsString, useQueryState } from "nuqs" + +import { Tabs, TabsContent, TabsList, TabsTrigger } from "ui/tabs" + +import { AddCommunity } from "../communities/AddCommunityDialog" +import { CommunityTable } from "../communities/CommunityTable" +import { BackupsPanel } from "./BackupsPanel" +import { MigrationsPanel } from "./MigrationsPanel" + +export const SuperadminDashboard = ({ + communities, + migrations, + migrationError, + backups, + backupConfig, +}: { + communities: TableCommunity[] + migrations: MigrationRow[] + migrationError?: string + backups: BackupRow[] + backupConfig: { + enabled: boolean + intervalHours: number + retentionDays: number + notificationEmail: string | null + } +}) => { + const [activeTab, setActiveTab] = useQueryState("tab", parseAsString.withDefault("communities")) + + return ( +
+

Superadmin

+ + + + Communities + Migrations + Backups + + + +
+

Communities

+ +
+ + +
+ + + + + + + + +
+
+ ) +} diff --git a/core/app/(user)/superadmin/actions.ts b/core/app/(user)/superadmin/actions.ts new file mode 100644 index 0000000000..f00b3887c8 --- /dev/null +++ b/core/app/(user)/superadmin/actions.ts @@ -0,0 +1,46 @@ +"use server" + +import { revalidatePath } from "next/cache" + +import { getLoginData } from "~/lib/authentication/loginData" +import { defineServerAction } from "~/lib/server/defineServerAction" +import { resolveFailedMigration, runMigrations } from "~/lib/server/migrate" + +export const resolveMigration = defineServerAction(async function resolveMigration({ + migrationName, +}: { + migrationName: string +}) { + const { user } = await getLoginData() + + if (!user?.isSuperAdmin) { + return { title: "Unauthorized", error: "Must be a superadmin" } + } + + const result = await resolveFailedMigration(migrationName) + + if (result.error) { + return { title: "Failed to resolve migration", error: result.error } + } + + revalidatePath("/superadmin") +}) + +export const retryMigrations = defineServerAction(async function retryMigrations() { + const { user } = await getLoginData() + + if (!user?.isSuperAdmin) { + return { title: "Unauthorized", error: "Must be a superadmin" } + } + + try { + await runMigrations() + } catch (err) { + return { + title: "Migration failed", + error: err instanceof Error ? err.message : String(err), + } + } + + revalidatePath("/superadmin") +}) diff --git a/core/app/(user)/superadmin/backup-actions.ts b/core/app/(user)/superadmin/backup-actions.ts new file mode 100644 index 0000000000..f60bae5b08 --- /dev/null +++ b/core/app/(user)/superadmin/backup-actions.ts @@ -0,0 +1,190 @@ +"use server" + +import { revalidatePath } from "next/cache" +import { DeleteObjectCommand, S3Client } from "@aws-sdk/client-s3" + +import { type BackupConfigId, type BackupRecordsId, BackupStatus } from "db/public" + +import { db } from "~/kysely/database" +import { getLoginData } from "~/lib/authentication/loginData" +import { env } from "~/lib/env/env" +import { defineServerAction } from "~/lib/server/defineServerAction" +import { getJobsClient } from "~/lib/server/jobs" +import { maybeWithTrx } from "~/lib/server/maybeWithTrx" + +const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error) { + return error.message + } + + return fallback +} + +const ensureSuperAdmin = async () => { + const { user } = await getLoginData() + if (!user?.isSuperAdmin) { + return null + } + + return user +} + +const getBackupS3Client = () => { + const bucket = env.S3_BACKUP_BUCKET + const region = env.S3_BACKUP_REGION + const accessKey = env.S3_BACKUP_ACCESS_KEY + const secretKey = env.S3_BACKUP_SECRET_KEY + const endpoint = env.S3_BACKUP_ENDPOINT + + const isMissingS3BackupConfig = !bucket || !region || !accessKey || !secretKey + + if (isMissingS3BackupConfig) { + return null + } + + return new S3Client({ + region, + endpoint, + credentials: { + accessKeyId: accessKey, + secretAccessKey: secretKey, + }, + forcePathStyle: true, + }) +} + +export const triggerBackup = defineServerAction(async function triggerBackup() { + const user = await ensureSuperAdmin() + if (!user) { + return { title: "Unauthorized", error: "Must be a superadmin" } + } + + const insertedBackup = await db + .insertInto("backup_records") + .values({ + filename: `queued-${Date.now()}.dump`, + s3Key: "queued", + status: BackupStatus.pending, + }) + .returning("id") + .executeTakeFirst() + + if (!insertedBackup) { + return { title: "Backup failed", error: "Failed to create backup record" } + } + + const jobsClient = await getJobsClient() + const scheduleResult = await jobsClient.scheduleBackup({ + backupId: insertedBackup.id, + }) + + if ("error" in scheduleResult) { + return { + title: "Backup failed", + error: getErrorMessage(scheduleResult.error, "Failed to queue backup"), + } + } + + revalidatePath("/superadmin") +}) + +export const deleteBackup = defineServerAction(async function deleteBackup({ + backupId, +}: { + backupId: string +}) { + const user = await ensureSuperAdmin() + if (!user) { + return { title: "Unauthorized", error: "Must be a superadmin" } + } + + await maybeWithTrx(db, async (trx) => { + const backupRecord = await trx + .selectFrom("backup_records") + .selectAll() + .where("id", "=", backupId as BackupRecordsId) + .executeTakeFirst() + + if (!backupRecord) { + return { title: "Not found", error: "Backup record not found" } + } + + await trx + .deleteFrom("backup_records") + .where("id", "=", backupId as BackupRecordsId) + .execute() + + const s3Client = getBackupS3Client() + if (s3Client && env.S3_BACKUP_BUCKET && backupRecord.s3Key !== "queued") { + await s3Client.send( + new DeleteObjectCommand({ + Bucket: env.S3_BACKUP_BUCKET, + Key: backupRecord.s3Key, + }) + ) + } + }) + + revalidatePath("/superadmin") +}) + +export const updateBackupConfig = defineServerAction(async function updateBackupConfig({ + enabled, + intervalHours, + retentionDays, + notificationEmail, +}: { + enabled: boolean + intervalHours: number + retentionDays: number + notificationEmail: string | null +}) { + const user = await ensureSuperAdmin() + if (!user) { + return { title: "Unauthorized", error: "Must be a superadmin" } + } + + const existingConfig = await db.selectFrom("backup_config").select("id").executeTakeFirst() + + if (existingConfig) { + await db + .updateTable("backup_config") + .set({ enabled, intervalHours, retentionDays, notificationEmail }) + .where("id", "=", existingConfig.id) + .execute() + } else { + await db + .insertInto("backup_config") + .values({ enabled, intervalHours, retentionDays, notificationEmail }) + .execute() + } + + revalidatePath("/superadmin") +}) + +export const getBackups = async () => { + return db.selectFrom("backup_records").selectAll().orderBy("createdAt", "desc").execute() +} + +export const getBackupConfig = async () => { + const config = await db + .selectFrom("backup_config") + .selectAll() + .orderBy("updatedAt", "desc") + .limit(1) + .executeTakeFirst() + + if (config) { + return config + } + + return { + id: "00000000-0000-0000-0000-000000000000" as BackupConfigId, + enabled: false, + intervalHours: 24, + retentionDays: 14, + notificationEmail: null as string | null, + createdAt: new Date(), + updatedAt: new Date(), + } +} diff --git a/core/app/(user)/superadmin/page.tsx b/core/app/(user)/superadmin/page.tsx new file mode 100644 index 0000000000..d9c25fdedf --- /dev/null +++ b/core/app/(user)/superadmin/page.tsx @@ -0,0 +1,60 @@ +import type { TableCommunity } from "../communities/getCommunityTableColumns" + +import { redirect } from "next/navigation" + +import { db } from "~/kysely/database" +import { getPageLoginData } from "~/lib/authentication/loginData" +import { getMigrationStatus } from "~/lib/server/migrate" +import { getBackupConfig, getBackups } from "./backup-actions" +import { SuperadminDashboard } from "./SuperadminDashboard" + +export const metadata = { + title: "Superadmin", +} + +export const dynamic = "force-dynamic" + +export default async function Page() { + const { user } = await getPageLoginData() + + if (!user.isSuperAdmin) { + redirect("/") + } + + const [communities, migrationResult, backups, backupConfig] = await Promise.all([ + db + .selectFrom("communities") + .select([ + "communities.id", + "communities.name", + "communities.slug", + "communities.avatar", + "createdAt", + ]) + .execute(), + getMigrationStatus(), + getBackups(), + getBackupConfig(), + ]) + + const tableCommunities = communities.map((c) => ({ + id: c.id, + name: c.name, + slug: c.slug, + avatar: c.avatar, + created: new Date(c.createdAt), + })) satisfies TableCommunity[] + + const migrations = "migrations" in migrationResult ? (migrationResult.migrations ?? []) : [] + const migrationError = "error" in migrationResult ? migrationResult.error : undefined + + return ( + + ) +} diff --git a/core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts b/core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts index 7d831ababa..ee7af588b7 100644 --- a/core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts +++ b/core/app/api/v0/c/[communitySlug]/site/[...ts-rest]/route.ts @@ -3,7 +3,7 @@ import type { ExpressionBuilder, ExpressionWrapper } from "kysely" import { createNextHandler } from "@ts-rest/serverless/next" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { siteApi, TOTAL_PUBS_COUNT_HEADER } from "contracts" import { ApiAccessScope, @@ -237,7 +237,7 @@ const handler = createNextHandler( const pubContext = buildInterpolationContext({ community, pub, - env: { PUBPUB_URL: env.PUBPUB_URL }, + env: { PUBSTAR_URL: env.PUBSTAR_URL }, useDummyValues: true, }) diff --git a/core/app/c/[communitySlug]/LoginSwitcher.tsx b/core/app/c/[communitySlug]/LoginSwitcher.tsx index 362cb80fac..61bac02f02 100644 --- a/core/app/c/[communitySlug]/LoginSwitcher.tsx +++ b/core/app/c/[communitySlug]/LoginSwitcher.tsx @@ -52,6 +52,23 @@ export default async function LoginSwitcher() { Settings + + {user.isSuperAdmin && ( + + )} { return await userCan( Capabilities.editCommunity, @@ -196,6 +208,35 @@ const adminLinks: LinkGroupDefinition = { ], } +const getEnvironmentToolLinks = (): EnvironmentToolLink[] => { + const pubpubUrl = new URL(env.PUBSTAR_URL) + + const flags = env.FLAGS?.get("show-test-only-tools") + if (!flags) { + return [] + } + + const isLocalhost = pubpubUrl.hostname === "localhost" || pubpubUrl.hostname === "127.0.0.1" + const inbucketUrl = + env.INBUCKET_URL ?? (isLocalhost ? "http://localhost:54324" : `${pubpubUrl.origin}/emails`) + const mockNotifyUrl = isLocalhost + ? "http://localhost:4001/mock-notify" + : `${pubpubUrl.origin}/mock-notify` + + return [ + { + href: inbucketUrl, + text: "Email Inbox", + icon: , + }, + { + href: mockNotifyUrl, + text: "COAR Server", + icon: , + }, + ] +} + export const COLLAPSIBLE_TYPE: Parameters[0]["collapsible"] = "icon" const Links = ({ @@ -345,6 +386,7 @@ const LinkGroup = async ({ const SideNav: React.FC = async ({ community, availableCommunities }) => { const { user } = await getLoginData() + const environmentToolLinks = getEnvironmentToolLinks() if (!user) { return null @@ -368,6 +410,35 @@ const SideNav: React.FC = async ({ community, availableCommunities }) => + {environmentToolLinks.length > 0 ? ( + + + Dev tools + + These are only visible in development or preview environments. + + + + + {environmentToolLinks.map((toolLink) => ( + + + + {toolLink.icon} + {toolLink.text} + + + + + ))} + + + + ) : null} diff --git a/core/app/c/[communitySlug]/developers/docs/openapi.json/openApi.ts b/core/app/c/[communitySlug]/developers/docs/openapi.json/openApi.ts index 51c534e8e7..05ff996c74 100644 --- a/core/app/c/[communitySlug]/developers/docs/openapi.json/openApi.ts +++ b/core/app/c/[communitySlug]/developers/docs/openapi.json/openApi.ts @@ -6,6 +6,8 @@ import { generateOpenApi } from "@ts-rest/open-api" import { siteApi } from "contracts" +import { env } from "~/lib/env/env" + type TraverseContractResult = "SKIP" | "CONTINUE" | "STOP" const traverseContract = ( @@ -40,7 +42,7 @@ export const createOpenApiDocument = (communitySlug?: string): OpenAPIObject => contact: { name: "PubPub", url: "https://help.pubpub.org", - email: "hello@pubpub.org", + email: "hello@pubstar.org", }, license: { name: "GPL v2.0+", @@ -54,7 +56,7 @@ export const createOpenApiDocument = (communitySlug?: string): OpenAPIObject => description: "The development API server", }, { - url: "https://app.pubpub.org/", + url: env.PUBSTAR_URL, description: "The production API server", }, ], diff --git a/core/app/c/[communitySlug]/settings/community/actions.ts b/core/app/c/[communitySlug]/settings/community/actions.ts index 4a9882428f..12390b80ed 100644 --- a/core/app/c/[communitySlug]/settings/community/actions.ts +++ b/core/app/c/[communitySlug]/settings/community/actions.ts @@ -15,7 +15,7 @@ import { db } from "~/kysely/database" import { getLoginData } from "~/lib/authentication/loginData" import { userCan } from "~/lib/authorization/capabilities" import { env } from "~/lib/env/env" -import { ApiError, deleteFileFromS3 } from "~/lib/server" +import { ApiError, deleteFileFromS3, normalizeAssetUrl } from "~/lib/server" import { generateSignedCommunityAvatarUploadUrl } from "~/lib/server/assets" import { getCommunitySlug } from "~/lib/server/cache/getCommunitySlug" import { updateCommunity } from "~/lib/server/community" @@ -118,12 +118,13 @@ export const updateCommunityAvatar = defineServerAction(async function updateCom .executeTakeFirst() const currentAvatar = community?.avatar + const normalizedAvatar = fileName ? normalizeAssetUrl(fileName) : null if (fileName) { await updateCommunity( { id: communityId, - avatar: fileName, + avatar: normalizedAvatar, }, trx ) @@ -140,7 +141,7 @@ export const updateCommunityAvatar = defineServerAction(async function updateCom const doesAvatarContainCommunityId = currentAvatar?.includes( `/avatars/communities/${communityId}/` ) - if (currentAvatar && currentAvatar !== fileName && doesAvatarContainCommunityId) { + if (currentAvatar && currentAvatar !== normalizedAvatar && doesAvatarContainCommunityId) { await deleteFileFromS3(currentAvatar) } }) diff --git a/core/app/components/pubs/CreatePubButton.tsx b/core/app/components/pubs/CreatePubButton.tsx index 759992edbb..ed1356ab3d 100644 --- a/core/app/components/pubs/CreatePubButton.tsx +++ b/core/app/components/pubs/CreatePubButton.tsx @@ -3,8 +3,7 @@ import type { ButtonProps } from "ui/button" import type { PubTypeWithForm } from "~/lib/authorization/capabilities" import { Suspense } from "react" - -import { Plus } from "ui/icon" +import { Star } from "lucide-react" import { getLoginData } from "~/lib/authentication/loginData" import { getCreatablePubTypes } from "~/lib/authorization/capabilities" @@ -109,7 +108,7 @@ export const CreatePubButton = async (props: Props) => { buttonText={props.text ?? "Create"} buttonVariant={props.variant} className={props.className} - icon={} + icon={} id={id} param="create-pub-form" title="Create Pub" diff --git a/core/app/components/pubs/PubCard/PubCardClient.tsx b/core/app/components/pubs/PubCard/PubCardClient.tsx index 97d2fb2be9..28a3d3c207 100644 --- a/core/app/components/pubs/PubCard/PubCardClient.tsx +++ b/core/app/components/pubs/PubCard/PubCardClient.tsx @@ -193,7 +193,7 @@ export const PubCardHeader = ({ return (
{children}
diff --git a/core/app/components/pubs/PubEditor/actions.ts b/core/app/components/pubs/PubEditor/actions.ts index 0e1f097234..2046c00a92 100644 --- a/core/app/components/pubs/PubEditor/actions.ts +++ b/core/app/components/pubs/PubEditor/actions.ts @@ -14,7 +14,12 @@ import { getLoginData } from "~/lib/authentication/loginData" import { userCan, userCanCreatePub, userCanEditPub } from "~/lib/authorization/capabilities" import { parseRichTextForPubFieldsAndRelatedPubs } from "~/lib/fields/richText" import { createLastModifiedBy } from "~/lib/lastModifiedBy" -import { ApiError, createPubRecursiveNew, makeFileUploadPermanent } from "~/lib/server" +import { + ApiError, + createPubRecursiveNew, + makeFileUploadPermanent, + normalizeAssetUrl, +} from "~/lib/server" import { findCommunityBySlug } from "~/lib/server/community" import { defineServerAction } from "~/lib/server/defineServerAction" import { getForm, grantFormAccess } from "~/lib/server/form" @@ -93,29 +98,45 @@ export const createPubRecursive = defineServerAction(async function createPubRec userId: user.id as UsersId, }) - const fileUploads: { fileName: string; tempUrl: string }[] = [] + const fileUploadsByTempUrl = new Map() const fileUploadSchema = getJsonSchemaByCoreSchemaType(CoreSchemaType.FileUpload) const filteredValues = values ? Object.fromEntries( - Object.entries(values).filter(([slug, value]) => { + Object.entries(values).flatMap(([slug, value]) => { const element = form.elements.find((element) => element.slug === slug) if (!element) { - return false + return [] } + if ( - element.schemaName === CoreSchemaType.FileUpload && - Value.Check(fileUploadSchema, value) && - value.length > 0 + element.schemaName !== CoreSchemaType.FileUpload || + !Value.Check(fileUploadSchema, value) ) { - fileUploads.push({ - tempUrl: value[0].fileUploadUrl, - fileName: value[0].fileName, - }) + return [[slug, value]] } - return true + + const normalizedFiles = value.map((file) => { + const normalizedUrl = normalizeAssetUrl(file.fileUploadUrl) + const isTemporaryUpload = normalizedUrl.includes("/temporary/") + + if (isTemporaryUpload && !fileUploadsByTempUrl.has(normalizedUrl)) { + fileUploadsByTempUrl.set(normalizedUrl, { + tempUrl: normalizedUrl, + fileName: file.fileName, + }) + } + + return { + ...file, + fileUploadUrl: normalizedUrl, + } + }) + + return [[slug, normalizedFiles]] }) ) : {} + const fileUploads = Array.from(fileUploadsByTempUrl.values()) logger.debug({ msg: "creating pub", filteredValues, fileUploads }) try { // need this in order to test it properly @@ -205,10 +226,6 @@ export const updatePub = defineServerAction(async function updatePub({ return ApiError.NOT_LOGGED_IN } - if (!community) { - return ApiError.COMMUNITY_NOT_FOUND - } - if (!formSlug) { return ApiError.UNAUTHORIZED } @@ -244,7 +261,7 @@ export const updatePub = defineServerAction(async function updatePub({ }) const normalizedValues = normalizePubValues(processedVals) - const fileUploads: { fileName: string; tempUrl: string }[] = [] + const fileUploadsByTempUrl = new Map() const fileUploadSchema = getJsonSchemaByCoreSchemaType(CoreSchemaType.FileUpload) for (const { slug, value, relatedPubId } of normalizedValues) { @@ -253,44 +270,59 @@ export const updatePub = defineServerAction(async function updatePub({ continue } + let valueToPersist = value if ( element.schemaName === CoreSchemaType.FileUpload && - Value.Check(fileUploadSchema, value) && - value.length > 0 && - // otherwise it will try to make permanent already-permanent files - // FIXME: better check than this - value[0].fileUploadUrl.includes("temporary") + Value.Check(fileUploadSchema, value) ) { - fileUploads.push({ - tempUrl: value[0].fileUploadUrl, - fileName: value[0].fileName, + const normalizedFiles = value.map((file) => { + const normalizedUrl = normalizeAssetUrl(file.fileUploadUrl) + const isTemporaryUpload = normalizedUrl.includes("/temporary/") + + if (isTemporaryUpload && !fileUploadsByTempUrl.has(normalizedUrl)) { + fileUploadsByTempUrl.set(normalizedUrl, { + tempUrl: normalizedUrl, + fileName: file.fileName, + }) + } + + return { + ...file, + fileUploadUrl: normalizedUrl, + } }) + + valueToPersist = normalizedFiles as typeof value } if (relatedPubId) { - updateQuery.relate(slug, value, relatedPubId, { + updateQuery.relate(slug, valueToPersist, relatedPubId, { replaceExisting: false, }) } else { - updateQuery.set(slug, value) + updateQuery.set(slug, valueToPersist) } } + const fileUploads = Array.from(fileUploadsByTempUrl.values()) + for (const { slug, relatedPubId } of deleted) { updateQuery.unrelate(slug, relatedPubId) } - const [pub] = await Promise.all([ - updateQuery.executeAndReturnPub(), - ...fileUploads.map(({ fileName, tempUrl }) => + const pub = await updateQuery.executeAndReturnPub() + + await Promise.all( + fileUploads.map(({ fileName, tempUrl }) => makeFileUploadPermanent({ pubId, tempUrl, fileName, userId: loginData.user.id, }) - ), - ]) + ) + ) + return pub } catch (error) { logger.error(error) diff --git a/core/app/globals.css b/core/app/globals.css index eaafb9f848..22fd5dfc33 100644 --- a/core/app/globals.css +++ b/core/app/globals.css @@ -1,6 +1,6 @@ @import "tailwindcss"; -@import "@pubpub/tailwind/style.css"; +@import "@pubstar/tailwind/style.css"; @source "../../packages/ui/src/**/*.{ts,tsx}"; diff --git a/core/cache-handler.mjs b/core/cache-handler.mjs index fc7de10345..ea4102e2c1 100644 --- a/core/cache-handler.mjs +++ b/core/cache-handler.mjs @@ -102,7 +102,7 @@ function createRedisHandler({ } const revalidatedTagsKey = keyPrefix + REVALIDATED_TAGS_KEY return { - name: "pubpub-redis-strings", + name: "pubstar-redis-strings", async get(key, { implicitTags }) { try { assertClientIsReady() diff --git a/core/instrumentation.node.mts b/core/instrumentation.node.mts index 0c42a1bf66..980075e5ec 100644 --- a/core/instrumentation.node.mts +++ b/core/instrumentation.node.mts @@ -9,11 +9,11 @@ import { env } from "./lib/env/env" // function hook() { logger.info("Running instrumentation hook for nodejs...") -if (env.NODE_ENV === "production") { +if (env.NODE_ENV === "production" && !env.DISABLE_TELEMETRY) { logger.info("Instrumenting Sentry...") Sentry.init({ dsn: "https://5012643b47ea6b2c8917f14442066f23@o31718.ingest.sentry.io/4505959187480576", - + environment: env.ENV_NAME, // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, diff --git a/core/instrumentation.ts b/core/instrumentation.ts index efaa830c1e..351dc481c3 100644 --- a/core/instrumentation.ts +++ b/core/instrumentation.ts @@ -16,13 +16,17 @@ export async function register() { } logger.info(`Registering instrumentation hook for ${process.env.NEXT_RUNTIME}`) + if (process.env.NEXT_RUNTIME === "nodejs") { + if (!process.env.SKIP_MIGRATIONS) { + const { runMigrations } = await import("./lib/server/migrate") + await runMigrations() + } + if (process.env.NODE_ENV === "development") { - logger.info( - "NEXT_RUNTIME is `nodejs` and NODE_ENV is `development`; skipping OTEL + Sentry registration." - ) return } + await import("./instrumentation.node.mts") } else { logger.info("NEXT_RUNTIME is not `nodejs`; skipping OTEL registration.") diff --git a/core/lib/api.ts b/core/lib/api.ts index c032b72eb5..f4d03bf7d6 100644 --- a/core/lib/api.ts +++ b/core/lib/api.ts @@ -5,7 +5,7 @@ import { siteApi } from "contracts" import { env } from "./env/env" export const client = initTsrReactQuery(siteApi, { - baseUrl: typeof window === "undefined" ? env.PUBPUB_URL : window.location.origin, + baseUrl: typeof window === "undefined" ? env.PUBSTAR_URL : window.location.origin, }) export const RETRY_COUNT = 3 diff --git a/core/lib/authentication/actions.ts b/core/lib/authentication/actions.ts index f1bbcbb799..a21b5cba70 100644 --- a/core/lib/authentication/actions.ts +++ b/core/lib/authentication/actions.ts @@ -36,6 +36,7 @@ import { } from "~/lib/server/user" import { LAST_VISITED_COOKIE } from "../../app/components/LastVisitedCommunity/constants" import { createSiteBuilderToken } from "../server/apiAccessTokens" +import { normalizeAssetUrl } from "../server/assets" import { findCommunityBySlug } from "../server/community" import * as Email from "../server/email" import { insertCommunityMemberships, selectCommunityMemberships } from "../server/member" @@ -408,6 +409,7 @@ export const publicSignup = defineServerAction(async function signup(props: { } const trx = db.transaction() + const normalizedAvatar = props.avatar ? normalizeAssetUrl(props.avatar) : null const newUser = await trx.execute(async (trx) => { try { @@ -421,7 +423,7 @@ export const publicSignup = defineServerAction(async function signup(props: { generateUserSlug({ firstName: props.firstName, lastName: props.lastName }), passwordHash: await createPasswordHash(props.password), isVerified: false, - avatar: props.avatar ?? null, + avatar: normalizedAvatar, }, trx ).executeTakeFirstOrThrow((err) => { @@ -528,6 +530,7 @@ export const legacySignup = defineServerAction(async function signup( } const trx = db.transaction() + const normalizedAvatar = props.avatar ? normalizeAssetUrl(props.avatar) : null const updatedUser = await trx.execute(async (trx) => { const changedEmail = user.email !== props.email @@ -537,7 +540,7 @@ export const legacySignup = defineServerAction(async function signup( firstName: props.firstName, lastName: props.lastName, email: props.email, - avatar: props.avatar ?? null, + avatar: normalizedAvatar, // If the user changed the email that they signed up with, make // sure they are not verified (magic-link login will mark them as verified) ...(changedEmail ? { isVerified: false } : {}), @@ -638,6 +641,9 @@ export const initializeSetup = defineServerAction(async function initializeSetup userAvatar, } = parsed.data + const normalizedUserAvatar = userAvatar ? normalizeAssetUrl(userAvatar) : null + const normalizedCommunityAvatar = communityAvatar ? normalizeAssetUrl(communityAvatar) : null + let result: { user: Users; community: Communities } try { result = await db.transaction().execute(async (trx) => { @@ -653,7 +659,7 @@ export const initializeSetup = defineServerAction(async function initializeSetup passwordHash, isSuperAdmin: true, isVerified: true, - avatar: userAvatar ?? null, + avatar: normalizedUserAvatar, }) .returningAll() .executeTakeFirstOrThrow() @@ -663,7 +669,7 @@ export const initializeSetup = defineServerAction(async function initializeSetup .values({ name: communityName, slug: slugifyString(communitySlug), - avatar: communityAvatar ?? null, + avatar: normalizedCommunityAvatar, }) .returningAll() .executeTakeFirstOrThrow() diff --git a/core/lib/authentication/createMagicLink.ts b/core/lib/authentication/createMagicLink.ts index e88af2bea8..fe5ea7620b 100644 --- a/core/lib/authentication/createMagicLink.ts +++ b/core/lib/authentication/createMagicLink.ts @@ -25,5 +25,5 @@ export const createMagicLink = async (options: NativeMagicLinkOptions, trx = db) } export const constructMagicLink = (token: string, path: `/${string}`) => { - return `${env.PUBPUB_URL}/magic-link?token=${token}&redirectTo=${encodeURIComponent(path)}` + return `${env.PUBSTAR_URL}/magic-link?token=${token}&redirectTo=${encodeURIComponent(path)}` } diff --git a/core/lib/editor/to-html.test.ts b/core/lib/editor/to-html.test.ts index 62885083d8..1574a17271 100644 --- a/core/lib/editor/to-html.test.ts +++ b/core/lib/editor/to-html.test.ts @@ -1,13 +1,12 @@ import { describe, expect, it } from "vitest" -// @ts-expect-error -import ponies from "../../prisma/seeds/ponies.snippet.html?raw" +import { poniesText } from "../../prisma/seeds/ponies.snippet" import { processEditorHTML } from "./process-editor-html" import { htmlToProsemirrorServer, prosemirrorToHTMLServer } from "./serialize-server" describe("renderNodeToHTML", () => { it("should be able to round trip a node and not lose any information", async () => { - const html = ponies + const html = poniesText expect(html).toBeDefined() diff --git a/core/lib/env/env.ts b/core/lib/env/env.ts index 9fb30ab208..4c32aa17c5 100644 --- a/core/lib/env/env.ts +++ b/core/lib/env/env.ts @@ -15,21 +15,45 @@ export const env = createEnv({ }, server: { SELF_HOSTED: z.string().optional(), + DISABLE_TELEMETRY: z.coerce + .boolean() + .optional() + .describe( + "Whether or not to disable telemetry. By default Pubstar sends anonymous error and performance data to Honeycomb and Sentry." + ), API_KEY: z.string(), - ASSETS_BUCKET_NAME: z.string(), - ASSETS_REGION: z.string(), - ASSETS_UPLOAD_KEY: z.string(), - ASSETS_UPLOAD_SECRET_KEY: z.string(), - ASSETS_STORAGE_ENDPOINT: z.string().url().optional(), - ASSETS_PUBLIC_ENDPOINT: z + S3_BUCKET_NAME: z.string().describe("The name of the S3 bucket to use for storing assets."), + S3_REGION: z + .string() + .describe( + "The region of the S3 bucket to use for storing assets. If not known, use 'us-east-1'." + ), + S3_ACCESS_KEY: z + .string() + .describe("The access key for the S3 bucket to use for storing assets."), + S3_SECRET_KEY: z + .string() + .describe("The secret key for the S3 bucket to use for storing assets."), + S3_ENDPOINT: z + .string() + .url() + .optional() + .describe( + "The API endpoint for the S3 bucket to use for storing assets. This can differ from the public endpoint if you are using a private S3 bucket." + ), + S3_PUBLIC_ENDPOINT: z .string() .url() .optional() - .transform((val) => - !val && process.env.ASSETS_STORAGE_ENDPOINT - ? process.env.ASSETS_STORAGE_ENDPOINT - : val + .describe( + "The public endpoint for the S3 bucket to use for storing assets. This is the endpoint that will be used to access the assets from the web, and is what your users will see when they view the assets." ), + S3_BACKUP_BUCKET: z.string().optional(), + S3_BACKUP_REGION: z.string().optional(), + S3_BACKUP_ACCESS_KEY: z.string().optional(), + S3_BACKUP_SECRET_KEY: z.string().optional(), + S3_BACKUP_ENDPOINT: z.string().url().optional(), + S3_BACKUP_KEY_PREFIX: z.string().optional(), /** * Whether or not to verbosely log `memoize` cache hits and misses */ @@ -41,17 +65,16 @@ export const env = createEnv({ KYSELY_DEBUG: z.string().optional(), KYSELY_ARTIFICIAL_LATENCY: z.coerce.number().optional(), LOG_LEVEL: z.enum(["benchmark", "debug", "info", "warn", "error"]).optional(), - MAILGUN_SMTP_PASSWORD: selfHostedOptional(z.string()), - MAILGUN_SMTP_USERNAME: selfHostedOptional(z.string()), - MAILGUN_SMTP_HOST: selfHostedOptional(z.string()), - MAILGUN_SMTP_PORT: selfHostedOptional(z.string()), - MAILGUN_SMTP_FROM: z.string().optional(), - MAILGUN_SMTP_FROM_NAME: z.string().optional(), - MAILGUN_INSECURE_SENDMAIL: z.string().optional(), - MAILGUN_SMTP_SECURITY: z.enum(["ssl", "tls", "none"]).optional(), + SMTP_PASSWORD: selfHostedOptional(z.string()), + SMTP_USERNAME: selfHostedOptional(z.string()), + SMTP_HOST: selfHostedOptional(z.string()), + SMTP_PORT: selfHostedOptional(z.string()), + SMTP_FROM: selfHostedOptional(z.string().email()), + SMTP_FROM_NAME: selfHostedOptional(z.string()), + SMTP_SECURITY: z.enum(["ssl", "tls", "none"]).optional(), OTEL_SERVICE_NAME: z.string().optional(), HONEYCOMB_API_KEY: z.string().optional(), - PUBPUB_URL: z.string().url(), + PUBSTAR_URL: z.string().url(), INBUCKET_URL: z.string().url().optional(), CI: z.string().or(z.boolean()).optional(), GCLOUD_KEY_FILE: selfHostedOptional(z.string()), diff --git a/core/lib/env/flags.ts b/core/lib/env/flags.ts index 075eecc536..bd497bae29 100644 --- a/core/lib/env/flags.ts +++ b/core/lib/env/flags.ts @@ -43,6 +43,21 @@ const flagSchema = z.union([ .transform((s) => (s ? s.split("+").map((a) => a.trim()) : [])) .pipe(actionSchema.array()), ]), + z.tuple([ + z.literal("http-allowed-domains"), + z + .string() + .optional() + .transform((s) => + s + ? s + .split("+") + .map((domain) => domain.trim()) + .filter((domain) => domain.length > 0) + : [] + ) + .pipe(z.string().array()), + ]), z.tuple([ z.literal("invites"), z.string().transform(flagStateToBoolean).optional().default("on"), @@ -51,6 +66,10 @@ const flagSchema = z.union([ z.literal("uploads"), z.string().transform(flagStateToBoolean).optional().default("on"), ]), + z.tuple([ + z.literal("show-test-only-tools"), + z.string().transform(flagStateToBoolean).optional().default("off"), + ]), ]) export const flagsSchema = z diff --git a/core/lib/redirect.ts b/core/lib/redirect.ts index d9e112449e..6966552d58 100644 --- a/core/lib/redirect.ts +++ b/core/lib/redirect.ts @@ -11,8 +11,8 @@ export const createRedirectUrl = (redirectTo: string, searchParams?: Record { url.searchParams.append(key, value) @@ -22,5 +22,5 @@ export const createRedirectUrl = (redirectTo: string, searchParams?: Record { // check if minio is up - if (!env.ASSETS_STORAGE_ENDPOINT) { + if (!env.S3_ENDPOINT) { throw new Error( "You should only run this test against a local minio instance, not to prod S3" ) } - const check = await fetch(env.ASSETS_STORAGE_ENDPOINT, { + const check = await fetch(env.S3_ENDPOINT, { method: "OPTIONS", }) diff --git a/core/lib/server/assets.ts b/core/lib/server/assets.ts index 06df71d6ea..9c642a8ab8 100644 --- a/core/lib/server/assets.ts +++ b/core/lib/server/assets.ts @@ -13,6 +13,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner" import { sql } from "kysely" import { logger } from "logger" +import { tryCatch } from "utils/try-catch" import { db } from "~/kysely/database" import { env } from "../env/env" @@ -22,6 +23,202 @@ import { getCommunitySlug } from "./cache/getCommunitySlug" let s3Client: S3Client export type FileMetadata = InputTypeForCoreSchemaType[number] +export type SignedUploadTarget = { + signedUrl: string + publicUrl: string +} + +const trimSlashes = (value: string) => value.replace(/^\/+|\/+$/g, "") + +const trimLeadingSlashes = (value: string) => value.replace(/^\/+/, "") + +const isBucketHost = (hostname: string) => + hostname === env.S3_BUCKET_NAME || hostname.startsWith(`${env.S3_BUCKET_NAME}.`) + +const shouldIncludeBucketInPath = (baseUrl: URL) => { + const basePath = trimSlashes(baseUrl.pathname) + const basePathIncludesBucket = + basePath === env.S3_BUCKET_NAME || basePath.startsWith(`${env.S3_BUCKET_NAME}/`) + + if (basePathIncludesBucket) { + return false + } + + return !isBucketHost(baseUrl.hostname) +} + +const getPathRelativeToBase = (url: URL, baseUrl: string) => { + const base = new URL(baseUrl) + + if (url.origin !== base.origin) { + return null + } + + const basePath = trimSlashes(base.pathname) + const path = trimSlashes(url.pathname) + + if (!basePath) { + return path + } + + if (path === basePath) { + return "" + } + + if (!path.startsWith(`${basePath}/`)) { + return null + } + + return path.slice(basePath.length + 1) +} + +const buildS3PublicUrl = (key: string) => { + const publicEndpoint = env.S3_PUBLIC_ENDPOINT || env.S3_ENDPOINT + const normalizedKey = trimLeadingSlashes(key) + + if (!publicEndpoint) { + return `https://${env.S3_BUCKET_NAME}.s3.${env.S3_REGION}.amazonaws.com/${normalizedKey}` + } + + const baseUrl = new URL(publicEndpoint) + const basePath = trimSlashes(baseUrl.pathname) + const shouldIncludeBucket = shouldIncludeBucketInPath(baseUrl) + + const pathSegments = [ + basePath, + shouldIncludeBucket ? env.S3_BUCKET_NAME : null, + normalizedKey, + ].filter(Boolean) + + baseUrl.pathname = `/${pathSegments.join("/")}` + + return baseUrl.toString() +} + +const getS3ObjectKeyCandidates = (fileUrl: string) => { + try { + const parsedUrl = new URL(fileUrl) + const bucket = env.S3_BUCKET_NAME + const candidates = new Set() + + const addCandidate = (candidate: string | null) => { + if (!candidate) { + return + } + + const normalized = trimLeadingSlashes(candidate) + + if (normalized) { + candidates.add(normalized) + } + } + + const addBucketRelativeCandidate = (candidate: string | null) => { + if (!candidate) { + return + } + + const normalized = trimLeadingSlashes(candidate) + if (!normalized.startsWith(`${bucket}/`)) { + return + } + + addCandidate(normalized.slice(bucket.length + 1)) + } + + const addCandidateFromBaseUrl = (baseUrl: string | undefined) => { + if (!baseUrl) { + return + } + + const relativePath = getPathRelativeToBase(parsedUrl, baseUrl) + addBucketRelativeCandidate(relativePath) + addCandidate(relativePath) + } + + addCandidateFromBaseUrl(env.S3_PUBLIC_ENDPOINT) + addCandidateFromBaseUrl(env.S3_ENDPOINT) + + const path = trimLeadingSlashes(parsedUrl.pathname) + + if (isBucketHost(parsedUrl.hostname)) { + addCandidate(path) + } + + addBucketRelativeCandidate(path) + + return Array.from(candidates) + } catch (_err) { + return [] + } +} + +const getS3ObjectKey = (fileUrl: string) => { + const candidates = getS3ObjectKeyCandidates(fileUrl) + + return candidates[0] ?? null +} + +const getTemporaryS3ObjectKey = (fileUrl: string) => { + const candidates = getS3ObjectKeyCandidates(fileUrl) + + return candidates.find((candidate) => candidate.startsWith("temporary/")) ?? null +} + +const isS3GetObjectPermissionError = (error: unknown) => { + if (!error || typeof error !== "object") { + return false + } + + const s3Error = error as { name?: string; message?: string } + return s3Error.name === "AccessDenied" && s3Error.message?.includes("s3:GetObject") === true +} + +const copyObjectViaPublicEndpoint = async ({ + sourceKey, + destinationKey, + s3Client, +}: { + sourceKey: string + destinationKey: string + s3Client: S3Client +}) => { + const sourceUrl = buildS3PublicUrl(sourceKey) + const sourceResponse = await fetch(sourceUrl, { cache: "no-store" }) + + if (!sourceResponse.ok) { + throw new Error(`Unable to download source object from public endpoint: ${sourceUrl}`) + } + + const sourceContentType = + sourceResponse.headers.get("content-type") ?? "application/octet-stream" + const sourceBody = Buffer.from(await sourceResponse.arrayBuffer()) + + const upload = new Upload({ + client: s3Client, + params: { + Bucket: env.S3_BUCKET_NAME, + Key: destinationKey, + Body: sourceBody, + ContentType: sourceContentType, + }, + queueSize: 3, + partSize: 1024 * 1024 * 5, + leavePartsOnError: false, + }) + + await upload.done() +} + +export const normalizeAssetUrl = (fileUrl: string) => { + const key = getS3ObjectKey(fileUrl) + + if (!key) { + return fileUrl + } + + return buildS3PublicUrl(key) +} /** * Useful for migrating data from other S3 buckets to the new one. @@ -63,13 +260,13 @@ export const generateMetadataFromS3 = async ( } export const getS3Client = () => { - const region = env.ASSETS_REGION - const key = env.ASSETS_UPLOAD_KEY - const secret = env.ASSETS_UPLOAD_SECRET_KEY + const region = env.S3_REGION + const key = env.S3_ACCESS_KEY + const secret = env.S3_SECRET_KEY logger.info({ msg: "Initializing S3 client", - endpoint: env.ASSETS_STORAGE_ENDPOINT, + endpoint: env.S3_ENDPOINT, region, key, secret, @@ -79,13 +276,13 @@ export const getS3Client = () => { } s3Client = new S3Client({ - endpoint: env.ASSETS_STORAGE_ENDPOINT, + endpoint: env.S3_ENDPOINT, region: region, credentials: { accessKeyId: key, secretAccessKey: secret, }, - forcePathStyle: !!env.ASSETS_STORAGE_ENDPOINT, // Required for MinIO + forcePathStyle: !!env.S3_ENDPOINT, // Required for MinIO }) logger.info({ @@ -95,23 +292,22 @@ export const getS3Client = () => { return s3Client } -// we create a separate client for generating signed URLs that uses the public endpoint -// this is bc, when using `minio` locally, the server -// uses `minio:9000`, but for the client this does not make sense -export const getPublicS3Client = () => { - const region = env.ASSETS_REGION - const key = env.ASSETS_UPLOAD_KEY - const secret = env.ASSETS_UPLOAD_SECRET_KEY - const publicEndpoint = env.ASSETS_PUBLIC_ENDPOINT || env.ASSETS_STORAGE_ENDPOINT +// signed urls are generated against the storage endpoint. +// this endpoint must be reachable by clients uploading directly to s3. +export const getSignedUploadS3Client = () => { + const region = env.S3_REGION + const key = env.S3_ACCESS_KEY + const secret = env.S3_SECRET_KEY + const uploadEndpoint = env.S3_ENDPOINT return new S3Client({ - endpoint: publicEndpoint, + endpoint: uploadEndpoint, region: region, credentials: { accessKeyId: key, secretAccessKey: secret, }, - forcePathStyle: !!publicEndpoint, // Required for MinIO + forcePathStyle: !!uploadEndpoint, // Required for MinIO }) } @@ -123,29 +319,27 @@ export const generateSignedAssetUploadUrl = async ( const communitySlug = await getCommunitySlug() const key = `${kind === "temporary" ? "temporary/" : ""}${communitySlug}/${userId}/${crypto.randomUUID()}/${fileName}` - const client = getPublicS3Client() // use public client for signed URLs + return generateSignedUploadUrl(key, kind === "temporary" ? { expiresIn: 3600 } : undefined) +} - const bucket = env.ASSETS_BUCKET_NAME +const generateSignedUploadUrl = async ( + key: string, + options?: { expiresIn?: number } +): Promise => { + const client = getSignedUploadS3Client() const command = new PutObjectCommand({ - Bucket: bucket, + Bucket: env.S3_BUCKET_NAME, Key: key, }) - return await getSignedUrl( - client, - command, - kind === "temporary" ? { expiresIn: 3600 } : undefined - ) -} + const signedUrl = options?.expiresIn + ? await getSignedUrl(client, command, { expiresIn: options.expiresIn }) + : await getSignedUrl(client, command) -const generateSignedUploadUrl = async (key: string) => { - const client = getPublicS3Client() - const bucket = env.ASSETS_BUCKET_NAME - const command = new PutObjectCommand({ - Bucket: bucket, - Key: key, - }) - return await getSignedUrl(client, command) + return { + signedUrl, + publicUrl: buildS3PublicUrl(key), + } } export const generateSignedUserAvatarUploadUrl = async (userId: UsersId, fileName: string) => { @@ -174,10 +368,10 @@ export class InvalidFileUrlError extends Error { * Be very careful with this, always confirm whether the user is allowed to access this file */ export const deleteFileFromS3 = async (fileUrl: string) => { - const client = getPublicS3Client() - const bucket = env.ASSETS_BUCKET_NAME + const client = getS3Client() + const bucket = env.S3_BUCKET_NAME - const fileKey = fileUrl.split(new RegExp(`^.+${env.ASSETS_BUCKET_NAME}/`))[1] + const fileKey = getS3ObjectKey(fileUrl) if (!fileKey) { logger.error({ msg: "Unable to parse URL of uploaded file", fileUrl }) @@ -209,14 +403,15 @@ export const makeFileUploadPermanent = async ( }, trx = db ) => { - const matches = tempUrl.match(`(^.+${env.ASSETS_BUCKET_NAME}/)(temporary/.+)`) - const prefix = matches?.[1] - const source = matches?.[2] - if (!source || !fileName || !prefix) { + const source = getTemporaryS3ObjectKey(tempUrl) + + if (!source || !fileName) { logger.error({ msg: "Unable to parse URL of uploaded file", pubId, tempUrl }) throw new Error("Unable to parse URL of uploaded file") } + const newKey = `${pubId}/${fileName}` + const newFileUrl = buildS3PublicUrl(newKey) logger.info({ msg: "Retrieving S3 clients for makeFileUploadPermanent", @@ -233,8 +428,8 @@ export const makeFileUploadPermanent = async ( }) const copyCommand = new CopyObjectCommand({ - CopySource: `${env.ASSETS_BUCKET_NAME}/${source}`, - Bucket: env.ASSETS_BUCKET_NAME, + CopySource: `${env.S3_BUCKET_NAME}/${source}`, + Bucket: env.S3_BUCKET_NAME, Key: newKey, }) @@ -243,33 +438,73 @@ export const makeFileUploadPermanent = async ( copyCommand, }) - await s3Client.send(copyCommand) + const [copyErr] = await tryCatch(s3Client.send(copyCommand)) + + if (copyErr) { + if (!isS3GetObjectPermissionError(copyErr)) { + throw copyErr + } + + logger.warn({ + msg: "S3 copy requires get object permission, retrying with public endpoint download", + source, + newKey, + }) + + await copyObjectViaPublicEndpoint({ + sourceKey: source, + destinationKey: newKey, + s3Client, + }) + } logger.info({ msg: "Waiting for object to exist", newKey, }) - await waitUntilObjectExists( - { - client: s3Client, - maxWaitTime: 10, - minDelay: 1, - }, - { Bucket: env.ASSETS_BUCKET_NAME, Key: newKey } + const [waitErr] = await tryCatch( + waitUntilObjectExists( + { + client: s3Client, + maxWaitTime: 10, + minDelay: 1, + }, + { Bucket: env.S3_BUCKET_NAME, Key: newKey } + ) ) + + if (waitErr && !isS3GetObjectPermissionError(waitErr)) { + throw waitErr + } logger.debug({ msg: "successfully copied temp file to permanent directory", newKey, tempUrl }) await trx .updateTable("pub_values") .where("pub_values.pubId", "=", pubId) - //@ts-expect-error - .where((eb) => eb.ref("value", "->>").at(0).key("fileUploadUrl"), "=", tempUrl) - .set((eb) => ({ - value: eb.fn("jsonb_set", [ - "value", - sql.raw("'{0,fileUploadUrl}'"), - eb.val(JSON.stringify(prefix + newKey)), - ]), + .where( + (eb) => + eb.fn("jsonb_path_exists", [ + "value", + sql.raw("'$[*] ? (@.fileUploadUrl == $url)'"), + eb.val(JSON.stringify({ url: tempUrl })), + ]), + "=", + true + ) + .set(() => ({ + value: sql`( + select coalesce( + jsonb_agg( + case + when file_entry->>'fileUploadUrl' = ${tempUrl} + then jsonb_set(file_entry, '{fileUploadUrl}', to_jsonb(${newFileUrl}::text)) + else file_entry + end + ), + '[]'::jsonb + ) + from jsonb_array_elements(pub_values.value) as file_entry + )`, lastModifiedBy: createLastModifiedBy({ userId }), })) .execute() @@ -301,11 +536,11 @@ export const uploadFileToS3 = async ( contentType: string queueSize?: number partSize?: number - progressCallback?: (progress: any) => void + progressCallback?: (progress: unknown) => void } ): Promise => { const client = getS3Client() - const bucket = env.ASSETS_BUCKET_NAME + const bucket = env.S3_BUCKET_NAME const key = `${id}/${fileName}` const parallelUploads3 = new Upload({ @@ -329,7 +564,7 @@ export const uploadFileToS3 = async ( }) ) - const result = await parallelUploads3.done() + await parallelUploads3.done() - return result.Location! + return buildS3PublicUrl(key) } diff --git a/core/lib/server/cache/autoCache.ts b/core/lib/server/cache/autoCache.ts index b6bad2699d..bea68ed00d 100644 --- a/core/lib/server/cache/autoCache.ts +++ b/core/lib/server/cache/autoCache.ts @@ -4,12 +4,14 @@ import type { AutoCacheOptions, DirectAutoOutput, ExecuteFn, SQB } from "./types import { cache } from "react" import { logger } from "logger" +import { tryCatch } from "utils/try-catch" import { env } from "~/lib/env/env" import { createCacheTag, createCommunityCacheTags } from "./cacheTags" import { getCommunitySlug } from "./getCommunitySlug" import { memoize } from "./memoize" import { cachedFindTables, directAutoOutput } from "./sharedAuto" +import { shouldSkipCache as shouldSkipCacheStore } from "./skipCacheStore" import { getTablesWithLinkedTables } from "./specialTables" import { getTransactionStore, setTransactionStore } from "./transactionStorage" @@ -60,10 +62,33 @@ const executeWithCache = < options?: AutoCacheOptions ) => { const executeFn = cache(async (...args: Parameters) => { - const communitySlug = options?.communitySlug ?? (await getCommunitySlug()) - const compiledQuery = qb.compile() + const willSkipCacheStore = shouldSkipCacheStore("store") + const willSkipCacheFn = options?.skipCacheFn?.() + + const willSkipCache = willSkipCacheStore || willSkipCacheFn + + if (willSkipCache) { + logger.debug( + willSkipCacheStore + ? `Skipping cache for query ${compiledQuery.sql} because of skipCacheStore` + : `Skipping cache for query ${compiledQuery.sql} because of skipCacheFn` + ) + + return qb[method](...args) as ReturnType + } + + const [error, communitySlug] = options?.communitySlug + ? [null, options.communitySlug] + : await tryCatch(getCommunitySlug()) + + if (error) { + logger.error(`Error getting community slug: ${error.message}`) + logger.error(compiledQuery.sql) + throw error + } + const tables = await cachedFindTables(compiledQuery, "select") const allTables = getTablesWithLinkedTables(tables) @@ -88,7 +113,7 @@ const executeWithCache = < asOne ) - if (shouldSkipCache || options?.skipCacheFn?.()) { + if (shouldSkipCache) { if (env.CACHE_LOG) { logger.debug(`AUTOCACHE: Skipping cache for query: ${asOne}`) } diff --git a/core/lib/server/cache/autoRevalidate.ts b/core/lib/server/cache/autoRevalidate.ts index bfcd8cf961..c89015ad12 100644 --- a/core/lib/server/cache/autoRevalidate.ts +++ b/core/lib/server/cache/autoRevalidate.ts @@ -4,11 +4,13 @@ import type { AutoRevalidateOptions, DirectAutoOutput, ExecuteFn, QB } from "./t import { revalidatePath, revalidateTag } from "next/cache" import { logger } from "logger" +import { tryCatch } from "utils/try-catch" import { env } from "~/lib/env/env" import { getCommunitySlug } from "./getCommunitySlug" import { revalidateTagsForCommunity } from "./revalidate" import { cachedFindTables, directAutoOutput } from "./sharedAuto" +import { shouldSkipCache as shouldSkipCacheStore } from "./skipCacheStore" import { setTransactionStore } from "./transactionStorage" const executeWithRevalidate = < @@ -20,11 +22,28 @@ const executeWithRevalidate = < options?: AutoRevalidateOptions ) => { const executeFn = async (...args: Parameters) => { - const communitySlug = options?.communitySlug ?? (await getCommunitySlug()) + const compiledQuery = qb.compile() - const communitySlugs = Array.isArray(communitySlug) ? communitySlug : [communitySlug] + const willSkipCacheStore = shouldSkipCacheStore("invalidate") - const compiledQuery = qb.compile() + if (willSkipCacheStore) { + logger.debug( + `Skipping revalidation for query ${compiledQuery.sql} because of skipCacheStore` + ) + return qb[method](...args) as ReturnType + } + + const [error, communitySlug] = options?.communitySlug + ? [null, options.communitySlug] + : await tryCatch(getCommunitySlug()) + + if (error) { + logger.error(`Error getting community slug: ${error.message}`) + logger.error(compiledQuery.sql) + throw error + } + + const communitySlugs = Array.isArray(communitySlug) ? communitySlug : [communitySlug] const tables = await cachedFindTables(compiledQuery, "mutation") diff --git a/core/lib/server/cache/constants.ts b/core/lib/server/cache/constants.ts index b50ce4f301..67382c6e04 100644 --- a/core/lib/server/cache/constants.ts +++ b/core/lib/server/cache/constants.ts @@ -1,6 +1,6 @@ -export const PUBPUB_COMMUNITY_SLUG_COOKIE_NAME = "pubpub_community_slug" as const +export const PUBSTAR_COMMUNITY_SLUG_COOKIE_NAME = "pubstar_community_slug" as const -export const PUBPUB_COMMUNITY_SLUG_HEADER_NAME = "x-pubpub-community-slug" as const +export const PUBSTAR_COMMUNITY_SLUG_HEADER_NAME = "x-pubstar-community-slug" as const /** * In seconds diff --git a/core/lib/server/cache/getCommunitySlug.ts b/core/lib/server/cache/getCommunitySlug.ts index 2700a6e8f1..3c0e1861b2 100644 --- a/core/lib/server/cache/getCommunitySlug.ts +++ b/core/lib/server/cache/getCommunitySlug.ts @@ -2,7 +2,7 @@ import { cache } from "react" import { headers } from "next/headers" import { getParams } from "@nimpl/getters/get-params" -import { PUBPUB_COMMUNITY_SLUG_HEADER_NAME } from "./constants" +import { PUBSTAR_COMMUNITY_SLUG_HEADER_NAME } from "./constants" /** * Experimental and likely unstable way to get the community slug. @@ -33,7 +33,7 @@ export class NotInCommunityError extends Error { */ export const getCommunitySlug = cache(async () => { const header = await headers() - const communitySlugHeader = header.get(PUBPUB_COMMUNITY_SLUG_HEADER_NAME) + const communitySlugHeader = header.get(PUBSTAR_COMMUNITY_SLUG_HEADER_NAME) if (!communitySlugHeader) { throw new NotInCommunityError() } diff --git a/core/lib/server/cache/skipCacheStore.ts b/core/lib/server/cache/skipCacheStore.ts new file mode 100644 index 0000000000..1d690270b9 --- /dev/null +++ b/core/lib/server/cache/skipCacheStore.ts @@ -0,0 +1,54 @@ +import { AsyncLocalStorage } from "node:async_hooks" + +import { logger } from "logger" + +const SKIP_CACHE_OPTIONS = ["store", "invalidate", "both"] as const +export type SkipCacheOptions = (typeof SKIP_CACHE_OPTIONS)[number] + +// tags +export const skipCacheStore = new AsyncLocalStorage<{ + /** + * Whether to store the result in the cache or invalidate it + */ + shouldSkipCache: "store" | "invalidate" | "both" | undefined +}>() + +export const setSkipCacheStore = ({ shouldSkipCache }: { shouldSkipCache: SkipCacheOptions }) => { + const store = skipCacheStore.getStore() + + if (!store) { + logger.debug("no skip cache store found") + return + } + + store.shouldSkipCache = shouldSkipCache + + return store +} + +/** + * whether or not to skip the cache + */ +export const shouldSkipCache = (skipCacheOptions: SkipCacheOptions) => { + const store = skipCacheStore.getStore() + + if (!store) { + return false + } + + if (store.shouldSkipCache === "both") { + return true + } + + return store.shouldSkipCache === skipCacheOptions +} + +/** + * wrap a function with this to skip storing and/or invalidating the cache + * useful when outside of community contexts and you don't want to cache results + */ +export const withUncached = (fn: () => Promise, skipCacheOptions?: SkipCacheOptions) => { + return skipCacheStore.run({ shouldSkipCache: skipCacheOptions ?? "invalidate" }, async () => { + return fn() + }) +} diff --git a/core/lib/server/email.tsx b/core/lib/server/email.tsx index edbbe23038..251be37638 100644 --- a/core/lib/server/email.tsx +++ b/core/lib/server/email.tsx @@ -23,8 +23,8 @@ type RequiredOptions = Required> & XOR<{ html: string }, { text: string }> export const DEFAULT_OPTIONS = { - from: env.MAILGUN_SMTP_FROM ?? "hello@pubpub.org", - name: env.MAILGUN_SMTP_FROM_NAME ?? "PubPub Team", + from: env.SMTP_FROM, + name: env.SMTP_FROM_NAME, } as const function buildSend(emailPromise: () => Promise) { diff --git a/core/lib/server/invites/InviteService.ts b/core/lib/server/invites/InviteService.ts index ad308c6e53..e2fbb91f67 100644 --- a/core/lib/server/invites/InviteService.ts +++ b/core/lib/server/invites/InviteService.ts @@ -710,7 +710,7 @@ export namespace InviteService { searchParams.set("invite", inviteToken) searchParams.set("redirectTo", options.redirectTo) - return `${options?.absolute === false ? "" : env.PUBPUB_URL}/c/${communitySlug}/public/invite?${searchParams.toString()}` + return `${options?.absolute === false ? "" : env.PUBSTAR_URL}/c/${communitySlug}/public/invite?${searchParams.toString()}` } export function createInviteToken(invite: Invite) { diff --git a/core/lib/server/jobs.ts b/core/lib/server/jobs.ts index b81f35234c..a215854415 100644 --- a/core/lib/server/jobs.ts +++ b/core/lib/server/jobs.ts @@ -31,6 +31,7 @@ export const getScheduledAutomationJobKey = ({ export type JobsClient = { unscheduleJob(jobKey: string): Promise + scheduleBackup(options?: { backupId?: string }): Promise scheduleDelayedAutomation(options: { automationId: AutomationsId stageId: StagesId @@ -66,6 +67,33 @@ export const makeJobsClient = async (): Promise => { job: { key: jobKey }, }) }, + async scheduleBackup(options) { + const backupId = options?.backupId + + try { + const job = await workerUtils.addJob("createBackup", { + ...(backupId ? { backupId } : {}), + }) + + logger.info({ + msg: "Successfully scheduled backup job", + backupId, + jobId: job.id, + }) + + return job + } catch (err) { + logger.error({ + msg: "Error scheduling backup job", + backupId, + err: err instanceof Error ? err.message : String(err), + }) + + return { + error: err, + } as ClientException + } + }, async scheduleDelayedAutomation({ automationId, stageId, diff --git a/core/lib/server/mailgun.ts b/core/lib/server/mailgun.ts index f9ee2de922..84c9f29294 100644 --- a/core/lib/server/mailgun.ts +++ b/core/lib/server/mailgun.ts @@ -25,11 +25,11 @@ const NO_CONFIG = { } as const satisfies Partial const guessSecurityType = () => { - if (env.MAILGUN_SMTP_PORT === "465") { + if (env.SMTP_PORT === "465") { return "ssl" } - if (env.MAILGUN_SMTP_PORT === "587") { + if (env.SMTP_PORT === "587") { return "tls" } @@ -37,7 +37,7 @@ const guessSecurityType = () => { } const getSecurityConfig = () => { - const securityType = env.MAILGUN_SMTP_SECURITY ?? guessSecurityType() + const securityType = env.SMTP_SECURITY ?? guessSecurityType() if (securityType === "ssl") { return SSL_CONFIG @@ -53,14 +53,9 @@ const getSecurityConfig = () => { export const getSmtpClient = () => { const securityConfig = getSecurityConfig() - if ( - !env.MAILGUN_SMTP_HOST || - !env.MAILGUN_SMTP_PORT || - !env.MAILGUN_SMTP_USERNAME || - !env.MAILGUN_SMTP_PASSWORD - ) { + if (!env.SMTP_HOST || !env.SMTP_PORT || !env.SMTP_USERNAME || !env.SMTP_PASSWORD) { throw new Error( - "Missing required SMTP configuration. Please set MAILGUN_SMTP_HOST, MAILGUN_SMTP_PORT, MAILGUN_SMTP_USERNAME, and MAILGUN_SMTP_PASSWORD in order to send emails." + "Missing required SMTP configuration. Please set SMTP_HOST, SMTP_PORT, SMTP_USERNAME, and SMTP_PASSWORD in order to send emails." ) } @@ -68,12 +63,15 @@ export const getSmtpClient = () => { smtpclient = nodemailer.createTransport({ ...securityConfig, pool: true, - host: env.MAILGUN_SMTP_HOST, - port: parseInt(env.MAILGUN_SMTP_PORT, 10), - secure: securityConfig.secure && env.MAILGUN_SMTP_HOST !== "localhost" && !env.CI, + host: env.SMTP_HOST, + port: parseInt(env.SMTP_PORT, 10), + secure: + securityConfig.secure && + (env.SMTP_HOST !== "localhost" || env.SMTP_HOST !== "inbucket") && + !env.CI, auth: { - user: env.MAILGUN_SMTP_USERNAME, - pass: env.MAILGUN_SMTP_PASSWORD, + user: env.SMTP_USERNAME, + pass: env.SMTP_PASSWORD, }, }) } diff --git a/core/lib/server/migrate.ts b/core/lib/server/migrate.ts new file mode 100644 index 0000000000..99d323043b --- /dev/null +++ b/core/lib/server/migrate.ts @@ -0,0 +1,282 @@ +import type { Database } from "db/Database" +import type { PrismaMigrationsId } from "db/public" + +import { exec } from "node:child_process" +import { createHash, randomUUID } from "node:crypto" +import { existsSync, readdirSync, readFileSync } from "node:fs" +import { join, resolve } from "node:path" +import { promisify } from "node:util" +import { Kysely, PostgresDialect, sql } from "kysely" +import pg from "pg" + +import { tryCatch } from "utils/try-catch" + +const execAsync = promisify(exec) + +import { logger } from "logger" + +// arbitrary but stable id used to prevent concurrent migration runs across replicas +export const ADVISORY_LOCK_ID = 72_398_241 + +const CREATE_MIGRATIONS_TABLE = ` +CREATE TABLE IF NOT EXISTS "_prisma_migrations" ( + "id" VARCHAR(36) NOT NULL, + "checksum" VARCHAR(64) NOT NULL, + "finished_at" TIMESTAMPTZ, + "migration_name" VARCHAR(255) NOT NULL, + "logs" TEXT, + "rolled_back_at" TIMESTAMPTZ, + "started_at" TIMESTAMPTZ NOT NULL DEFAULT now(), + "applied_steps_count" INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +)` + +function sha256(content: string): string { + return createHash("sha256").update(content).digest("hex") +} + +async function waitForDatabase(pool: pg.Pool, maxAttempts = 30, intervalMs = 2000) { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const client = await pool.connect() + client.release() + return + } catch (err) { + if (attempt === maxAttempts) { + throw new Error( + `could not connect to database after ${maxAttempts} attempts: ${err}` + ) + } + + logger.info( + `database not ready, retrying in ${intervalMs}ms (${attempt}/${maxAttempts})...` + ) + await new Promise((r) => setTimeout(r, intervalMs)) + } + } +} + +async function autoResolveFailedMigrations(db: Kysely) { + const failed = await db + .selectFrom("_prisma_migrations") + .select(["id", "migration_name", "logs"]) + .where("finished_at", "is", null) + .where("rolled_back_at", "is", null) + .execute() + + if (failed.length === 0) { + return + } + + // migrations run inside a postgres transaction, so if they fail the sql + // changes are fully rolled back. it is safe to mark them as rolled_back + // and let the next attempt re-apply them cleanly. + const names = failed.map((r) => r.migration_name).join(", ") + logger.warn( + `auto-resolving ${failed.length} failed migration(s): ${names}. ` + + `these will be retried on this startup.` + ) + + for (const row of failed) { + logger.info( + `marking migration ${row.migration_name} as rolled back` + + (row.logs ? ` (previous error: ${row.logs.slice(0, 200)})` : "") + ) + + await db + .updateTable("_prisma_migrations") + .set({ rolled_back_at: new Date() }) + .where("id", "=", row.id) + .execute() + } +} + +export async function getMigrationStatus() { + const connectionString = process.env.DATABASE_URL + if (!connectionString) { + return { error: "DATABASE_URL is not set" } + } + + const pool = new pg.Pool({ connectionString, max: 1 }) + const db = new Kysely({ dialect: new PostgresDialect({ pool }) }) + + try { + const migrations = await db + .selectFrom("_prisma_migrations") + .selectAll() + .orderBy("started_at", "asc") + .execute() + + return { migrations } + } catch (err) { + return { error: String(err) } + } finally { + await db.destroy() + } +} + +export async function resolveFailedMigration(migrationName: string) { + const connectionString = process.env.DATABASE_URL + if (!connectionString) { + return { error: "DATABASE_URL is not set" } + } + + const pool = new pg.Pool({ connectionString, max: 1 }) + const db = new Kysely({ dialect: new PostgresDialect({ pool }) }) + + try { + const migration = await db + .selectFrom("_prisma_migrations") + .selectAll() + .where("migration_name", "=", migrationName) + .where("finished_at", "is", null) + .where("rolled_back_at", "is", null) + .executeTakeFirst() + + if (!migration) { + return { error: `no failed migration found with name: ${migrationName}` } + } + + await db + .updateTable("_prisma_migrations") + .set({ rolled_back_at: new Date() }) + .where("id", "=", migration.id) + .execute() + + return { resolved: migrationName } + } finally { + await db.destroy() + } +} + +export async function runMigrations() { + const connectionString = process.env.DATABASE_URL + if (!connectionString) { + throw new Error("DATABASE_URL is required to run migrations") + } + + const migrationsDir = process.env.MIGRATIONS_DIR + ? resolve(process.env.MIGRATIONS_DIR) + : resolve(process.cwd(), "prisma", "migrations") + + if (!existsSync(migrationsDir)) { + logger.warn(`migrations directory not found at ${migrationsDir}, skipping`) + return + } + + const shouldReset = !!process.env.DB_RESET + const shouldSeed = !!process.env.DB_SEED + + logger.info(`running migrations from ${migrationsDir}`) + + // max: 1 ensures every operation (kysely typed queries + raw pool.query) + // shares the same underlying connection session, keeping the advisory lock valid + const pool = new pg.Pool({ connectionString, max: 1 }) + const db = new Kysely({ dialect: new PostgresDialect({ pool }) }) + + try { + await waitForDatabase(pool) + + await sql`SELECT pg_advisory_lock(${sql.lit(ADVISORY_LOCK_ID)})`.execute(db) + + if (shouldReset) { + logger.info("resetting database (DB_RESET is set)") + await pool.query("DROP SCHEMA public CASCADE") + await pool.query("DROP SCHEMA IF EXISTS graphile_worker CASCADE") + await pool.query("CREATE SCHEMA public") + } + + await pool.query(CREATE_MIGRATIONS_TABLE) + + await autoResolveFailedMigrations(db) + + const applied = await db + .selectFrom("_prisma_migrations") + .select("migration_name") + .where("finished_at", "is not", null) + .where("rolled_back_at", "is", null) + .execute() + + const appliedNames = new Set(applied.map((r) => r.migration_name)) + + const dirs = readdirSync(migrationsDir, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + .sort() + + let count = 0 + + for (const dir of dirs) { + if (appliedNames.has(dir)) { + continue + } + + const sqlPath = join(migrationsDir, dir, "migration.sql") + if (!existsSync(sqlPath)) { + continue + } + + const migrationSql = readFileSync(sqlPath, "utf-8") + const checksum = sha256(migrationSql) + const id = randomUUID() as PrismaMigrationsId + + logger.info(`applying migration: ${dir}`) + + await db + .insertInto("_prisma_migrations") + .values({ id, checksum, migration_name: dir }) + .execute() + + try { + await pool.query(migrationSql) + } catch (err) { + await db + .updateTable("_prisma_migrations") + .set({ logs: String(err) }) + .where("id", "=", id) + .execute() + + logger.error({ msg: `Error applying migration ${dir}`, sql: migrationSql, err }) + + throw err + } + + await db + .updateTable("_prisma_migrations") + .set({ finished_at: new Date(), applied_steps_count: 1 }) + .where("id", "=", id) + .execute() + + count++ + } + + if (count > 0) { + logger.info(`applied ${count} migration(s)`) + } else { + logger.info("database is up to date, no pending migrations") + } + + if (shouldSeed) { + logger.info("running database seed (DB_SEED is set)") + const { seed } = await import("~/prisma/seed") + + const { withUncached } = await import("~/lib/server/cache/skipCacheStore") + await withUncached(seed, "both") + + logger.info(`Clearing cache...`) + const [error, output] = await tryCatch( + execAsync("echo 'FLUSHALL' | nc $VALKEY_HOST 6379") + ) + + if (error || output.stderr) { + logger.error(`Error clearing cache: ${error?.message || output?.stderr}`) + } else { + logger.info(`Cache cleared: ${output.stdout}`) + } + } + + await sql`SELECT pg_advisory_unlock(${sql.lit(ADVISORY_LOCK_ID)})`.execute(db) + } finally { + await db.destroy() + } +} diff --git a/core/lib/server/render/pub/renderMarkdownWithPub.ts b/core/lib/server/render/pub/renderMarkdownWithPub.ts index 60e8e12807..fdfcec1072 100644 --- a/core/lib/server/render/pub/renderMarkdownWithPub.ts +++ b/core/lib/server/render/pub/renderMarkdownWithPub.ts @@ -138,14 +138,14 @@ const visitLinkDirective = (node: Directive, context: utils.RenderWithPubContext // All directives are considered parent nodes assert(isParent(node)) let href: string - // :link{email=all@pubpub.org} + // :link{email=all@pubstar.org} if ("email" in attrs) { // The `email` attribute must have a value. For example, :link{email=""} // is invalid. const email = expect(attrs.email, 'Unexpected missing value in ":link{email=?}" directive') href = utils.renderLink(context, { email }) // If the email has no label, default to the email address, e.g. - // :link{email=all@pubpub.org} -> :link[all@pubpub.org]{email=all@pubpub.org} + // :link{email=all@pubstar.org} -> :link[all@pubstar.org]{email=all@pubstar.org} if (node.children.length === 0 && attrs.text === undefined) { node.children.push({ type: "text", value: email }) } diff --git a/core/lib/server/render/pub/renderWithPubUtils.ts b/core/lib/server/render/pub/renderWithPubUtils.ts index 1746403637..7e62b8b893 100644 --- a/core/lib/server/render/pub/renderWithPubUtils.ts +++ b/core/lib/server/render/pub/renderWithPubUtils.ts @@ -256,7 +256,7 @@ export const renderLink = (context: RenderWithPubContext, options: LinkOptions) } else if (isLinkFieldOptions(options)) { href = getPubValue(context, options.field, options.rel).value as string } else if (isLinkPageOptions(options)) { - const baseUrl = `${env.PUBPUB_URL}/c/${context.communitySlug}` + const baseUrl = `${env.PUBSTAR_URL}/c/${context.communitySlug}` let tempHref = baseUrl diff --git a/core/middleware.ts b/core/middleware.ts index 42a327c95d..b5dec6a185 100644 --- a/core/middleware.ts +++ b/core/middleware.ts @@ -2,7 +2,7 @@ import type { NextRequest } from "next/server" import { NextResponse } from "next/server" -import { PUBPUB_COMMUNITY_SLUG_HEADER_NAME } from "./lib/server/cache/constants" +import { PUBSTAR_COMMUNITY_SLUG_HEADER_NAME } from "./lib/server/cache/constants" const communityRouteRegexp = /^\/c\/([^/]*?)(?:$|\/)|\/api\/v\d\/c\/([^/]*?)\// @@ -27,7 +27,7 @@ const communitySlugMiddleware = async (request: NextRequest) => { const response = NextResponse.next() - response.headers.set(PUBPUB_COMMUNITY_SLUG_HEADER_NAME, communitySlug) + response.headers.set(PUBSTAR_COMMUNITY_SLUG_HEADER_NAME, communitySlug) return response } diff --git a/core/package.json b/core/package.json index 9f35e8ad21..536777d9ab 100644 --- a/core/package.json +++ b/core/package.json @@ -4,13 +4,12 @@ "version": "0.0.0", "private": true, "scripts": { - "playwright:test:base": "SKIP_VALIDATION=true NODE_OPTIONS='--import #register-loader' dotenv -e .env.local -e .env.development playwright test", + "playwright:test:base": "NODE_OPTIONS='--import #register-loader' dotenv -e .env.local -e .env.development playwright test", "playwright:test": "TEST_IGNORE=' ' pnpm playwright:test:base playwright/_first_tests/initialUser.spec.ts && TEST_IGNORE='**/initialUser.spec.ts' pnpm playwright:test:base", - "playwright:ui": "dotenv SKIP_VALIDATION=true NODE_OPTIONS='--import #register-loader' playwright test --ui", + "playwright:ui": "dotenv NODE_OPTIONS='--import #register-loader' playwright test --ui", "db:migrate-dev": "pnpm migrate-dev", "db:migrate-deploy": "pnpm migrate-deploy", "db:migrate-diff": "pnpm migrate-diff", - "db:migrate-docker": "pnpm migrate-docker", "db:prisma": "pnpm prisma", "db:studio": "pnpm prisma studio", "db:generate-history-table": "pnpm exec tsx prisma/scripts/history-tables/generate-history-table.mts", @@ -32,22 +31,21 @@ "migrate-dev": "dotenv -e .env.local -e .env.development prisma migrate dev && tsx prisma/consolidate-triggers.ts && pnpm --filter db make-kysely-types", "migrate-deploy": "dotenv -e .env.local -e .env.development prisma migrate deploy", "migrate-diff": "dotenv -e .env.local -e .env.development prisma migrate diff", - "migrate-docker": "dotenv -e .env.docker -- prisma migrate deploy", "prisma": "dotenv -e .env.local -e .env.development prisma", "prisma-studio": "dotenv -e .env.local -e .env.development prisma studio", "start": "next start", "start:dev": "dotenv -e .env.local -e .env.development pnpm start | pino-pretty", "reset-base": "PRISMA_SCHEMA_DISABLE_ADVISORY_LOCK=true dotenv -e .env.local -e .env.development prisma migrate reset -- --preview-feature --force | pino-pretty", "reset": "pnpm reset-base && pnpm clear-cache", - "test": "SKIP_VALIDATION=true vitest --logHeapUsage", - "test-run": "SKIP_VALIDATION=true vitest run --logHeapUsage", - "test-run-no-reset": "SKIP_VALIDATION=true SKIP_RESET=true vitest run", + "test": "vitest --logHeapUsage", + "test-run": "vitest run --logHeapUsage", + "test-run-no-reset": "SKIP_RESET=true vitest run", "test-run-with-jobs": "pnpm exec concurrently \"pnpm --filter jobs dev\" \"pnpm --filter core test-run\" --success=first -k", "type-check": "tsc --noEmit", "type-check:go": "tsgo --noEmit", "type-check-watch": "tsc --watch", "start-standalone": "node server.js", - "storybook": "SKIP_VALIDATION=true PUBPUB_URL=http://localhost:6006 storybook dev -p 6006 --no-open", + "storybook": "SKIP_VALIDATION=true PUBSTAR_URL=http://localhost:6006 storybook dev -p 6006 --no-open", "build-storybook": "SKIP_VALIDATION=true storybook build" }, "files": [".next", "public"], @@ -76,7 +74,7 @@ "@node-rs/argon2": "^1.8.3", "@opentelemetry/auto-instrumentations-node": "catalog:", "@prisma/client": "5.19.1", - "@pubpub/json-interpolate": "workspace:*", + "@pubstar/json-interpolate": "workspace:*", "@react-email/render": "^1.2.0", "@sentry/nextjs": "catalog:", "@sinclair/typebox": "catalog:", @@ -171,7 +169,7 @@ "@preconstruct/next": "^4.0.0", "@prisma/generator-helper": "^5.22.0", "@prisma/internals": "^5.22.0", - "@pubpub/tailwind": "workspace:*", + "@pubstar/tailwind": "workspace:*", "@storybook/addon-docs": "^9.1.2", "@storybook/addon-onboarding": "^9.1.2", "@storybook/addon-vitest": "9.0.8", diff --git a/core/playwright/coar-notify.spec.ts b/core/playwright/coar-notify.spec.ts index 6a4ac9d038..07b20fe5c7 100644 --- a/core/playwright/coar-notify.spec.ts +++ b/core/playwright/coar-notify.spec.ts @@ -120,12 +120,12 @@ const us1Seed = createSeed({ type: ["Offer", "coar-notify:ReviewAction"], id: "urn:uuid:{{ $.pub.id }}", actor: { - id: "{{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}", + id: "{{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}", type: "Service", name: "{{ $.community.name }}", }, object: { - id: "{{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + id: "{{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", type: ["Page", "sorg:AboutPage"], }, target: { @@ -157,7 +157,7 @@ const us1Seed = createSeed({ ], }, resolver: - '$.pub.id = {{ $replace($replace($.json.object.`as:inReplyTo`, $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pub/", "") }}', + '$.pub.id = {{ $replace($replace($.json.object.`as:inReplyTo`, $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pub/", "") }}', actions: [ { action: Action.createPub, @@ -396,7 +396,7 @@ const us2Seed = createSeed({ "type": ["Announce", "coar-notify:ReviewAction"], "id": "urn:uuid:" & $.pub.id, "object": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pubs/" & $.pub.id, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pubs/" & $.pub.id, "type": ["Page", "sorg:Review"], "as:inReplyTo": $.pub.values.sourceurl }, @@ -502,7 +502,7 @@ const us3Seed = createSeed({ "type": ["Announce", "coar-notify:ReviewAction"], "id": "urn:uuid:" & $.pub.id, "object": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pubs/" & $.pub.id, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pubs/" & $.pub.id, "type": ["Page", "sorg:Review"], "as:inReplyTo": $.pub.values.sourceurl }, diff --git a/core/playwright/login.flows.ts b/core/playwright/login.flows.ts index 4c05175d78..5287d94dc3 100644 --- a/core/playwright/login.flows.ts +++ b/core/playwright/login.flows.ts @@ -2,7 +2,7 @@ import type { Page } from "@playwright/test" import { LoginPage } from "./fixtures/login-page" -export const login = async (page: Page, email = "all@pubpub.org", password = "pubpub-all") => { +export const login = async (page: Page, email = "all@pubstar.org", password = "pubstar-all") => { const loginPage = new LoginPage(page) await loginPage.goto() await loginPage.loginAndWaitForNavigation(email, password) diff --git a/core/playwright/login.spec.ts b/core/playwright/login.spec.ts index 5dc11e1b41..a6c76ecd06 100644 --- a/core/playwright/login.spec.ts +++ b/core/playwright/login.spec.ts @@ -149,12 +149,12 @@ test.describe("Auth with lucia", () => { await page.waitForURL("/reset") // if it timesout here, the token is wrong await page.getByRole("textbox").click({ timeout: 1000 }) - await page.getByRole("textbox").fill("pubpub-some") + await page.getByRole("textbox").fill("pubstar-some") await page.getByRole("button", { name: "Set new password" }).click() await page.getByPlaceholder("name@example.com").click() await page.getByPlaceholder("name@example.com").fill(community.users.user3.email) await page.getByPlaceholder("name@example.com").press("Tab") - await page.getByLabel("Password").fill("pubpub-some") + await page.getByLabel("Password").fill("pubstar-some") await page.getByRole("button", { name: "Sign in" }).click() await page.waitForURL(/\/c\/.*\/stages/) diff --git a/core/playwright/verifyEmail.spec.ts b/core/playwright/verifyEmail.spec.ts index cae56fd638..857ac77d5a 100644 --- a/core/playwright/verifyEmail.spec.ts +++ b/core/playwright/verifyEmail.spec.ts @@ -92,7 +92,7 @@ test.describe("unverified user", () => { await loginPage.login(community.users.unverifiedJim.email, password) await page.waitForURL(`/verify`) - const inaccessiblePages = [`/c/${community.community.slug}/stages`, "/communities"] + const inaccessiblePages = [`/c/${community.community.slug}/stages`, "/superadmin"] for (const p of inaccessiblePages) { await page.goto(p) await page.waitForURL(`/verify?redirectTo=${encodeURIComponent(p)}`) @@ -150,7 +150,7 @@ test.describe("unverified user", () => { test("redirected to /verify page with redirect after signin", async ({ page }) => { const loginPage = new LoginPage(page) - const redirect = "?redirectTo=/communities" + const redirect = "?redirectTo=/superadmin" // Manually go to a page with a redirect url await page.goto(`/login${redirect}`) await loginPage.login(community.users.unverifiedJoe.email, password) @@ -161,7 +161,7 @@ test.describe("unverified user", () => { test("redirect url carries through after signing in and requesting a new link", async ({ page, }) => { - const redirect = "?redirectTo=/communities" + const redirect = "?redirectTo=/superadmin" await test.step("login with redirect", async () => { const loginPage = new LoginPage(page) await page.goto(`/login${redirect}`) @@ -170,7 +170,7 @@ test.describe("unverified user", () => { await page.waitForURL(`/verify${redirect}`) }) - const url = await test.step("request a verification code", async () => { + const _url = await test.step("request a verification code", async () => { await page.getByRole("button", { name: "Resend verification email" }).click() await page.getByRole("button", { name: "Success" }).waitFor() const { message } = await ( @@ -178,14 +178,9 @@ test.describe("unverified user", () => { ).getLatestMessage() const url = await getUrlFromInbucketMessage(message, page) expect(url).toBeTruthy() + expect(url).toContain("superadmin") return url as string }) - - await test.step("link in email redirects to redirect link", async () => { - await page.goto(url) - await page.waitForURL("/communities**") - // await page.getByText("Your email is now verified", { exact: true }).waitFor - }) }) test("going thru forget password flow verifies the user", async ({ page }) => { diff --git a/core/prisma/consolidated-triggers.sql b/core/prisma/consolidated-triggers.sql index c091919964..c4e4e588c6 100644 --- a/core/prisma/consolidated-triggers.sql +++ b/core/prisma/consolidated-triggers.sql @@ -1029,15 +1029,12 @@ DECLARE community_id text; BEGIN - -- Changed the first part of this conditional to return early if the operation is deleting a pub IF (NEW."pubId" IS NULL) THEN RETURN NEW; ELSE - -- Strip large JSON columns to avoid exceeding pg_notify's 8000 byte payload limit correct_row = to_jsonb(NEW) - 'config' - 'result' - 'json' - 'params'; END IF; - select into community_id "communityId" from "pubs" where "id" = correct_row->>'pubId'::text; PERFORM notify_change( diff --git a/core/prisma/migrations/20260408120000_rm_build_journal_site/migration.sql b/core/prisma/migrations/20260408120000_rm_build_journal_site/migration.sql index 61103ae9e7..89c81c3f15 100644 --- a/core/prisma/migrations/20260408120000_rm_build_journal_site/migration.sql +++ b/core/prisma/migrations/20260408120000_rm_build_journal_site/migration.sql @@ -1,20 +1,41 @@ /* - Warnings: + Warnings: - - The values [buildJournalSite] on the enum `Action` will be removed. If these variants are still used in the database, this will fail. + - The values [buildJournalSite] on the enum `Action` will be removed. If these variants are still used in the database, this will fail. + */ +-- Delete buildJournalSite action instances +DELETE FROM "action_instances" +WHERE "action" = 'buildJournalSite'; -*/ +DELETE FROM "action_config_defaults" +WHERE "action" = 'buildJournalSite'; --- Delete buildJournalSite action instances -DELETE FROM "action_instances" WHERE "action" = 'buildJournalSite'; -DELETE FROM "action_config_defaults" WHERE "action" = 'buildJournalSite'; +DELETE FROM "action_runs" +WHERE "action" = 'buildJournalSite'; -- AlterEnum BEGIN; -CREATE TYPE "Action_new" AS ENUM ('log', 'email', 'http', 'move', 'googleDriveImport', 'datacite', 'buildSite', 'createPub'); -ALTER TABLE "action_instances" ALTER COLUMN "action" TYPE "Action_new" USING ("action"::text::"Action_new"); -ALTER TABLE "action_config_defaults" ALTER COLUMN "action" TYPE "Action_new" USING ("action"::text::"Action_new"); +CREATE TYPE "Action_new" AS ENUM( + 'log', + 'email', + 'http', + 'move', + 'googleDriveImport', + 'datacite', + 'buildSite', + 'createPub' +); +ALTER TABLE "action_instances" + ALTER COLUMN "action" TYPE "Action_new" + USING ("action"::text::"Action_new"); +ALTER TABLE "action_config_defaults" + ALTER COLUMN "action" TYPE "Action_new" + USING ("action"::text::"Action_new"); +ALTER TABLE "action_runs" + ALTER COLUMN "action" TYPE "Action_new" + USING ("action"::text::"Action_new"); ALTER TYPE "Action" RENAME TO "Action_old"; ALTER TYPE "Action_new" RENAME TO "Action"; -DROP TYPE "Action_old"; +DROP TYPE "Action_old" CASCADE; COMMIT; + diff --git a/core/prisma/migrations/20260421120000_rewrite_asset_urls/migration.sql b/core/prisma/migrations/20260421120000_rewrite_asset_urls/migration.sql new file mode 100644 index 0000000000..60f993c1dd --- /dev/null +++ b/core/prisma/migrations/20260421120000_rewrite_asset_urls/migration.sql @@ -0,0 +1,49 @@ +-- rewrite asset URLs from assets.app.pubpub.org to assets.pubstar.org +-- in action_runs.result (jsonb) and pub_values.value (jsonb) +-- +-- pub_values has a history trigger that requires lastModifiedBy to carry +-- a fresh timestamp on every update (format: "{type}:{id}|{timestamp}"). +-- run one update per table so each matching row is rewritten once. +UPDATE + action_runs +SET + result = replace( + replace( + replace( + result::text, + 'assets.app.pubpub.org.s3.us-east-1.amazonaws.com', + 'assets.app.pubpub.org' + ), + 's3.us-east-1.amazonaws.com/assets.app.pubpub.org', + 'assets.app.pubpub.org' + ), + 'assets.app.pubpub.org', + 'assets.pubstar.org' + )::jsonb +WHERE + result::text LIKE '%assets.app.pubpub.org.s3.us-east-1.amazonaws.com%' + OR result::text LIKE '%s3.us-east-1.amazonaws.com/assets.app.pubpub.org%' + OR result::text LIKE '%assets.app.pubpub.org%'; + +UPDATE + pub_values +SET + value = replace( + replace( + replace( + value::text, + 'assets.app.pubpub.org.s3.us-east-1.amazonaws.com', + 'assets.app.pubpub.org' + ), + 's3.us-east-1.amazonaws.com/assets.app.pubpub.org', + 'assets.app.pubpub.org' + ), + 'assets.app.pubpub.org', + 'assets.pubstar.org' + )::jsonb, + "lastModifiedBy" = 'system|' || FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::text +WHERE + value::text LIKE '%assets.app.pubpub.org.s3.us-east-1.amazonaws.com%' + OR value::text LIKE '%s3.us-east-1.amazonaws.com/assets.app.pubpub.org%' + OR value::text LIKE '%assets.app.pubpub.org%'; + diff --git a/core/prisma/migrations/20260428162431_add_backup_system/migration.sql b/core/prisma/migrations/20260428162431_add_backup_system/migration.sql new file mode 100644 index 0000000000..9f8aeb59d0 --- /dev/null +++ b/core/prisma/migrations/20260428162431_add_backup_system/migration.sql @@ -0,0 +1,37 @@ +-- CreateEnum +CREATE TYPE "BackupStatus" AS ENUM( + 'pending', + 'in_progress', + 'completed', + 'failed' +); + +-- CreateTable +CREATE TABLE "backup_records"( + "id" text NOT NULL DEFAULT gen_random_uuid(), + "filename" text NOT NULL, + "s3Key" text NOT NULL, + "sizeBytes" bigint, + "status" "BackupStatus" NOT NULL DEFAULT 'pending', + "error" text, + "startedAt" timestamp(3), + "completedAt" timestamp(3), + "createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "backup_records_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "backup_config"( + "id" text NOT NULL DEFAULT gen_random_uuid(), + "enabled" boolean NOT NULL DEFAULT FALSE, + "intervalHours" integer NOT NULL DEFAULT 24, + "retentionDays" integer NOT NULL DEFAULT 14, + "createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "backup_config_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "backup_records_createdAt_idx" ON "backup_records"("createdAt"); + diff --git a/core/prisma/migrations/20260429123620_update_comments/migration.sql b/core/prisma/migrations/20260429123620_update_comments/migration.sql new file mode 100644 index 0000000000..6997e9a89c --- /dev/null +++ b/core/prisma/migrations/20260429123620_update_comments/migration.sql @@ -0,0 +1,265 @@ +-- generator-version: 1.0.0 + +-- Model member_groups comments + + + +-- Model community_memberships comments + + + +-- Model pub_memberships comments + + + +-- Model stage_memberships comments + + + +-- Model membership_capabilities comments + + + +-- Model invites_history comments + + + +-- Model pub_values_history comments + + + +-- Model users comments + +COMMENT ON COLUMN "users"."isProvisional" IS 'Indicates whether a user is provisional, meaning they were added through an invite and need to accept it to become a full user'; + + +-- Model sessions comments + +COMMENT ON COLUMN "sessions"."type" IS 'With what type of token is this session created? Used for determining on a page-by-page basis whether to allow a certain session to access it. For instance, a verify email token/session should not allow you to access the password reset page.'; + + +-- Model auth_tokens comments + + + +-- Model communities comments + + + +-- Model pubs comments + + + +-- Model pub_fields comments + + + +-- Model PubFieldSchema comments + +COMMENT ON COLUMN "PubFieldSchema"."schema" IS '@type(JSONSchemaType, ''ajv'', true, false, true)'; + + +-- Model pub_values comments + +COMMENT ON COLUMN "pub_values"."lastModifiedBy" IS '@type(LastModifiedBy, ''../types'', true, false, true)'; + + +-- Model pub_types comments + + + +-- Model _PubFieldToPubType comments + + + +-- Model stages comments + + + +-- Model PubsInStages comments + + + +-- Model move_constraint comments + + + +-- Model action_instances comments + +COMMENT ON COLUMN "action_instances"."config" IS '@type(BaseActionInstanceConfig, ''../types'', true, false, true)'; + + +-- Model action_config_defaults comments + + + +-- Model automation_runs comments + + + +-- Model action_runs comments + + + +-- Model automations comments + +COMMENT ON COLUMN "automations"."icon" IS '@type(IconConfig, ''../types'', true, false, true)'; + + +-- Model automation_triggers comments + + + +-- Model automation_condition_blocks comments + + + +-- Model automation_conditions comments + + + +-- Model forms comments + + + +-- Model form_elements comments + + + +-- Model api_access_tokens comments + + + +-- Model api_access_logs comments + + + +-- Model api_access_permissions comments + +COMMENT ON COLUMN "api_access_permissions"."constraints" IS '@type(ApiAccessPermissionConstraints, ''../types'', true, false, true)'; + + +-- Model invites comments + +COMMENT ON COLUMN "invites"."lastModifiedBy" IS '@type(LastModifiedBy, ''../types'', true, false, true)'; + + +-- Model invite_forms comments + + + +-- Model backup_records comments + + + +-- Model backup_config comments + + + +-- Enum Capabilities comments + + + + +-- Enum MemberRole comments + + + + +-- Enum MembershipType comments + + + + +-- Enum AuthTokenType comments + +COMMENT ON TYPE "AuthTokenType" IS '@property generic - For most use-cases. This will just authenticate you with a regular session. +@property passwordReset - For resetting your password only +@property signup - For signing up, but also when you''re invited to a community +@property verifyEmail - For verifying your email address'; + + +-- Enum CoreSchemaType comments + + + + +-- Enum OperationType comments + + + + +-- Enum Action comments + + + + +-- Enum ActionRunStatus comments + + + + +-- Enum AutomationEvent comments + + + + +-- Enum ConditionEvaluationTiming comments + + + + +-- Enum AutomationConditionBlockType comments + + + + +-- Enum AutomationConditionType comments + + + + +-- Enum FormAccessType comments + + + + +-- Enum StructuralFormElement comments + + + + +-- Enum ElementType comments + + + + +-- Enum InputComponent comments + + + + +-- Enum ApiAccessType comments + + + + +-- Enum ApiAccessScope comments + + + + +-- Enum InviteStatus comments + +COMMENT ON TYPE "InviteStatus" IS 'Status of an invite +@property created - The invite has been created, but not yet sent +@property pending - The invite has been sent, but not yet accepted +@property accepted - The invite has been accepted, but the relevant signup step has not been completed +@property completed - The invite has been accepted, and the relevant signup step has been completed +@property rejected - The invite has been rejected +@property revoked - The invite has been revoked by the user who created it, or by a sufficient authority'; + + +-- Enum BackupStatus comments + + diff --git a/core/prisma/migrations/20260430140000_add_backup_notification_email/migration.sql b/core/prisma/migrations/20260430140000_add_backup_notification_email/migration.sql new file mode 100644 index 0000000000..ac1792b509 --- /dev/null +++ b/core/prisma/migrations/20260430140000_add_backup_notification_email/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "backup_config" ADD COLUMN "notificationEmail" text; diff --git a/core/prisma/schema/comments/.comments-lock b/core/prisma/schema/comments/.comments-lock index 9ff290f7aa..6997e9a89c 100644 --- a/core/prisma/schema/comments/.comments-lock +++ b/core/prisma/schema/comments/.comments-lock @@ -148,6 +148,14 @@ COMMENT ON COLUMN "invites"."lastModifiedBy" IS '@type(LastModifiedBy, ''../type +-- Model backup_records comments + + + +-- Model backup_config comments + + + -- Enum Capabilities comments @@ -250,3 +258,8 @@ COMMENT ON TYPE "InviteStatus" IS 'Status of an invite @property completed - The invite has been accepted, and the relevant signup step has been completed @property rejected - The invite has been rejected @property revoked - The invite has been revoked by the user who created it, or by a sufficient authority'; + + +-- Enum BackupStatus comments + + diff --git a/core/prisma/schema/schema.dbml b/core/prisma/schema/schema.dbml index bda79ca8a1..2a73749c6a 100644 --- a/core/prisma/schema/schema.dbml +++ b/core/prisma/schema/schema.dbml @@ -572,6 +572,28 @@ Table invite_forms { } } +Table backup_records { + id String [pk] + filename String [not null] + s3Key String [not null] + sizeBytes BigInt + status BackupStatus [not null, default: 'pending'] + error String + startedAt DateTime + completedAt DateTime + createdAt DateTime [default: `now()`, not null] + updatedAt DateTime [default: `now()`, not null] +} + +Table backup_config { + id String [pk] + enabled Boolean [not null, default: false] + intervalHours Int [not null, default: 24] + retentionDays Int [not null, default: 14] + createdAt DateTime [default: `now()`, not null] + updatedAt DateTime [default: `now()`, not null] +} + Table MemberGroupToUser { usersId String [ref: > users.id] membergroupsId String [ref: > member_groups.id] @@ -768,6 +790,13 @@ Enum InviteStatus { revoked } +Enum BackupStatus { + pending + in_progress + completed + failed +} + Ref: member_groups.communityId > communities.id [delete: Cascade] Ref: community_memberships.communityId > communities.id [delete: Cascade] diff --git a/core/prisma/schema/schema.prisma b/core/prisma/schema/schema.prisma index 68ae9deab9..9ac1707dff 100644 --- a/core/prisma/schema/schema.prisma +++ b/core/prisma/schema/schema.prisma @@ -766,3 +766,38 @@ model InviteForm { @@unique([inviteId, formId, type]) @@map(name: "invite_forms") } + +enum BackupStatus { + pending + in_progress + completed + failed +} + +model BackupRecord { + id String @id @default(dbgenerated("gen_random_uuid()")) + filename String + s3Key String + sizeBytes BigInt? + status BackupStatus @default(pending) + error String? + startedAt DateTime? + completedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@index([createdAt]) + @@map(name: "backup_records") +} + +model BackupConfig { + id String @id @default(dbgenerated("gen_random_uuid()")) + enabled Boolean @default(false) + intervalHours Int @default(24) + retentionDays Int @default(14) + notificationEmail String? + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@map(name: "backup_config") +} diff --git a/core/prisma/seed.ts b/core/prisma/seed.ts index 1c27e0aff6..a5b9be236a 100644 --- a/core/prisma/seed.ts +++ b/core/prisma/seed.ts @@ -19,11 +19,7 @@ const coarUS2Id = "dd000002-dddd-4ddd-dddd-dddddddddddd" as CommunitiesId const coarUS3Id = "dd000003-dddd-4ddd-dddd-dddddddddddd" as CommunitiesId const coarUS4Id = "dd000004-dddd-4ddd-dddd-dddddddddddd" as CommunitiesId -async function main() { - // do not seed arcadia if the minimal seed flag is set - // this is because it will slow down ci/testing - // this flag is set in the `globalSetup.ts` file - // and in e2e.yml +export async function seed() { // eslint-disable-next-line no-restricted-properties const shouldSeedLegacy = !process.env.MINIMAL_SEED @@ -33,11 +29,6 @@ async function main() { connectionString: env.DATABASE_URL, }) - logger.info("drop existing jobs") - await workerUtils.withPgClient(async (client) => { - await client.query(`DROP SCHEMA graphile_worker CASCADE`) - }) - await workerUtils.migrate() // eslint-disable-next-line no-restricted-properties @@ -59,16 +50,22 @@ async function main() { await seedCoarUS3(coarUS3Id) await seedCoarUS4(coarUS4Id) } -main() - .then(async () => { - logger.info("Finished seeding, exiting...") - process.exit(0) - }) - .catch(async (e) => { - if (!isUniqueConstraintError(e)) { + +// cli entrypoint: only auto-run when executed directly as a script +const isCli = process.argv[1]?.endsWith("seed.ts") || process.argv[1]?.endsWith("seed.js") + +if (isCli) { + seed() + .then(async () => { + logger.info("Finished seeding, exiting...") + process.exit(0) + }) + .catch(async (e) => { + if (!isUniqueConstraintError(e)) { + logger.error(e) + process.exit(1) + } logger.error(e) - process.exit(1) - } - logger.error(e) - logger.info("Attempted to add duplicate entries, db is already seeded?") - }) + logger.info("Attempted to add duplicate entries, db is already seeded?") + }) +} diff --git a/core/prisma/seed/seedCommunity.db.test.ts b/core/prisma/seed/seedCommunity.db.test.ts index 7a0ce1631d..64d4726f3d 100644 --- a/core/prisma/seed/seedCommunity.db.test.ts +++ b/core/prisma/seed/seedCommunity.db.test.ts @@ -88,7 +88,7 @@ describe("seedCommunity", () => { config: { body: "hello nerd", subject: "hello nerd", - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", }, }, ], diff --git a/core/prisma/seed/seedCommunity.ts b/core/prisma/seed/seedCommunity.ts index 28ba880171..e4872c743e 100644 --- a/core/prisma/seed/seedCommunity.ts +++ b/core/prisma/seed/seedCommunity.ts @@ -1196,10 +1196,12 @@ export async function seedCommunity< logger.info( `${createdCommunity.name}: ${options?.parallelPubs ? "Parallelly" : "Sequentially"} - Creating ${createPubRecursiveInput.length} pubs` ) + if (options?.parallelPubs) { const input = createPubRecursiveInput.map((input) => createPubRecursiveNew({ ...input })) - setInterval(() => { + // using will auto clear the interval when the block is exited + using _interval = setInterval(() => { logger.info(`${createdCommunity.name}: Creating Pubs...`) }, 1000) diff --git a/core/prisma/seeds/coar-notify.ts b/core/prisma/seeds/coar-notify.ts index a9e927a4e9..4c54b48e91 100644 --- a/core/prisma/seeds/coar-notify.ts +++ b/core/prisma/seeds/coar-notify.ts @@ -12,7 +12,7 @@ import { env } from "~/lib/env/env" import { seedCommunity } from "../seed/seedCommunity" const WEBHOOK_PATH = "coar-inbox" -const REMOTE_INBOX_URL = "http://localhost:4001/api/inbox" +const REMOTE_INBOX_URL = process.env.MOCK_NOTIFY_INBOX_URL ?? "http://localhost:4001/api/inbox" const adminId = "dddddddd-dddd-4ddd-dddd-dddddddddd01" as UsersId const joeAuthorId = "dddddddd-dddd-4ddd-dddd-dddddddddd02" as UsersId @@ -64,6 +64,10 @@ const withBannerAndHead = ({ ].join(" ") } +const BASE_SITE_URL = env.S3_PUBLIC_ENDPOINT?.includes(env.S3_BUCKET_NAME) + ? `${env.S3_PUBLIC_ENDPOINT?.replace(/\/$/, "")}/sites` + : `${env.S3_ENDPOINT?.replace(/\/$/, "")}/${env.S3_BUCKET_NAME}/sites` + /** * User Story 1: Repository Author Requests Review * @@ -89,7 +93,7 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { id: communityId, name: "US1: Arcadia Science", slug: "coar-us1-arcadia", - avatar: `${env.PUBPUB_URL}/demo/croc.png`, + avatar: `${env.PUBSTAR_URL}/demo/croc.png`, }, pubFields: { Title: { schemaName: CoreSchemaType.String }, @@ -116,8 +120,8 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { id: adminId, firstName: "COAR", lastName: "Admin", - email: "coar-admin@pubpub.org", - password: "pubpub-coar", + email: "coar-admin@pubstar.org", + password: "pubstar-coar", role: MemberRole.admin, }, jillAdmin: { @@ -129,8 +133,8 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { id: joeAuthorId, firstName: "Joe", lastName: "Author", - email: "joe-author@pubpub.org", - password: "pubpub-joe", + email: "joe-author@pubstar.org", + password: "pubstar-joe", role: MemberRole.contributor, }, }, @@ -244,12 +248,12 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { "type": ["Offer", "coar-notify:ReviewAction"], "id": "urn:uuid:" & $.pub.id, "actor": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, "type": "Service", "name": $.community.name }, "object": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pub/" & $.pub.id, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pub/" & $.pub.id, "type": ["Page", "sorg:AboutPage"] }, "target": { @@ -258,8 +262,8 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { "type": "Service" }, "origin": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, - "inbox": $.env.PUBPUB_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, + "inbox": $.env.PUBSTAR_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", "type": "Service" } } >>>`, @@ -290,9 +294,9 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review offer accepted for: {{ $.pub.title }}", - body: "The review offer for **{{ $.pub.title }}** has been accepted.\n\nView the submission: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The review offer for **{{ $.pub.title }}** has been accepted.\n\nView the submission: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, { @@ -324,9 +328,9 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review offer rejected for: {{ $.pub.title }}", - body: "The review offer for **{{ $.pub.title }}** has been rejected.\n\nView the submission: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The review offer for **{{ $.pub.title }}** has been rejected.\n\nView the submission: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, { @@ -360,7 +364,7 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { ], }, resolver: - '$.pub.id = {{ $replace($replace($.json.object.`as:inReplyTo`, $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pub/", "") }}', + '$.pub.id = {{ $replace($replace($.json.object.`as:inReplyTo`, $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pub/", "") }}', actions: [ { action: Action.createPub, @@ -450,10 +454,10 @@ export async function seedCoarUS1(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Site published with new review for: {{ $.pub.title }}", - body: "The community site has been updated with a new review.\n\nReview: **{{ $.pub.title }}**\n\nView the pub: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: http://localhost:9000/assets.v7.pubpub.org/sites/coar-us1-arcadia/site/index.html", + body: `The community site has been updated with a new review.\n\nReview: **{{ $.pub.title }}**\n\nView the pub: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: /coar-us1-arcadia/site/index.html`, }, }, ], @@ -489,7 +493,7 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { Published: "dddddddd-0002-4ddd-dddd-dddddddddd15" as StagesId, } - const SITE_BASE = "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us2-unjournal/site" + const SITE_BASE = `${BASE_SITE_URL}/coar-us2-unjournal/site/` return seedCommunity( { @@ -497,7 +501,7 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { id: communityId, name: "US2: The Unjournal", slug: "coar-us2-unjournal", - avatar: `${env.PUBPUB_URL}/demo/croc.png`, + avatar: `${env.PUBSTAR_URL}/demo/croc.png`, }, pubFields: { Title: { schemaName: CoreSchemaType.String }, @@ -570,10 +574,10 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "New review request received: {{ $.json.object.id }}", - body: "A new review request has been received.\n\nObject: {{ $.json.object.id }}\n\nView: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}", + body: "A new review request has been received.\n\nObject: {{ $.json.object.id }}\n\nView: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}", }, }, ], @@ -652,15 +656,15 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { "type": "Accept", "id": "urn:uuid:" & $.pub.id & ":accept", "actor": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, "type": "Service", "name": $.community.name }, "inReplyTo": $payload.id, "object": $payload.object, "origin": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, - "inbox": $.env.PUBPUB_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, + "inbox": $.env.PUBSTAR_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", "type": "Service" }, "target": $payload.actor @@ -671,9 +675,9 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review request accepted: {{ $.pub.title }}", - body: "The review request **{{ $.pub.title }}** has been accepted.\n\nView: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The review request **{{ $.pub.title }}** has been accepted.\n\nView: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, ], @@ -755,15 +759,15 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { "type": "Reject", "id": "urn:uuid:" & $.pub.id & ":reject", "actor": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, "type": "Service", "name": $.community.name }, "inReplyTo": $payload.id, "object": $payload.object, "origin": { - "id": $.env.PUBPUB_URL & "/c/" & $.community.slug, - "inbox": $.env.PUBPUB_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", + "id": $.env.PUBSTAR_URL & "/c/" & $.community.slug, + "inbox": $.env.PUBSTAR_URL & "/api/v0/c/" & $.community.slug & "/site/webhook/${WEBHOOK_PATH}", "type": "Service" }, "target": $payload.actor, @@ -775,9 +779,9 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review request rejected: {{ $.pub.title }}", - body: "The review request **{{ $.pub.title }}** has been rejected.\n\nView: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The review request **{{ $.pub.title }}** has been rejected.\n\nView: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, ], @@ -868,8 +872,7 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { slug: "$.pub.id", transform: withBannerAndHead({ bannerText: "The Unjournal", - headExtra: - '""', + headExtra: `""`, content: [ "& '
'", "& '

' & $.pub.title & '

'", @@ -902,15 +905,15 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { "'{' &", '\'"@context": "https://w3id.org/docmaps/context.jsonld",\' &', '\'"type": "docmap",\' &', - "'\"id\": \"http://localhost:9000/assets.v7.pubpub.org/sites/coar-us2-unjournal/site/' & $.pub.id & '.docmap.json\",' &", + `'"id": "${SITE_BASE}" & $.pub.id & '.docmap.json",' &`, '\'"publisher": {"name": "\' & $.community.name & \'"},\' &', '\'"first-step": "_:b0",\' &', '\'"steps": {"_:b0": {"actions": [{"outputs": [{\' &', '\'"type": "review-article",\' &', "'\"as:inReplyTo\": \"' & $.pub.values.SourceURL & '\",' &", "'\"content\": [' &", - '\'{"type": "web-page", "url": "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us2-unjournal/site/\' & $.pub.id & \'/index.html"},\' &', - '\'{"type": "web-content", "url": "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us2-unjournal/site/\' & $.pub.id & \'/content/index.html"}\' &', + `'{"type": "web-page", "url": "${SITE_BASE}" & $.pub.id & '/index.html"},' &`, + `'{"type": "web-content", "url": "${SITE_BASE}" & $.pub.id & '/content/index.html"},' &`, "']' &", "'}]}]}}' &", "'}'", @@ -929,9 +932,9 @@ export async function seedCoarUS2(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review published: {{ $.pub.title }}", - body: `Review **{{ $.pub.title }}** has been published and announced.\n\nView the pub: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, + body: `Review **{{ $.pub.title }}** has been published and announced.\n\nView the pub: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, }, }, ], @@ -962,7 +965,7 @@ export async function seedCoarUS3(communityId?: CommunitiesId) { Published: "dddddddd-0003-4ddd-dddd-dddddddddd11" as StagesId, } - const SITE_BASE = "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us3-review-group/site" + const SITE_BASE = `${BASE_SITE_URL}/coar-us3-review-group/site/` return seedCommunity( { @@ -970,7 +973,7 @@ export async function seedCoarUS3(communityId?: CommunitiesId) { id: communityId, name: "US3: Review Group", slug: "coar-us3-review-group", - avatar: `${env.PUBPUB_URL}/demo/croc.png`, + avatar: `${env.PUBSTAR_URL}/demo/croc.png`, }, pubFields: { Title: { schemaName: CoreSchemaType.String }, @@ -1091,8 +1094,7 @@ export async function seedCoarUS3(communityId?: CommunitiesId) { slug: "$.pub.id", transform: withBannerAndHead({ bannerText: "Review Group", - headExtra: - '""', + headExtra: `""`, content: [ "& '
'", "& '

' & $.pub.title & '

'", @@ -1125,15 +1127,15 @@ export async function seedCoarUS3(communityId?: CommunitiesId) { "'{' &", '\'"@context": "https://w3id.org/docmaps/context.jsonld",\' &', '\'"type": "docmap",\' &', - "'\"id\": \"http://localhost:9000/assets.v7.pubpub.org/sites/coar-us3-review-group/site/' & $.pub.id & '.docmap.json\",' &", + `'"id": "${SITE_BASE}" & $.pub.id & '.docmap.json",' &`, '\'"publisher": {"name": "\' & $.community.name & \'"},\' &', '\'"first-step": "_:b0",\' &', '\'"steps": {"_:b0": {"actions": [{"outputs": [{\' &', '\'"type": "review-article",\' &', "'\"as:inReplyTo\": \"' & $.pub.values.SourceURL & '\",' &", "'\"content\": [' &", - '\'{"type": "web-page", "url": "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us3-review-group/site/\' & $.pub.id & \'/index.html"},\' &', - '\'{"type": "web-content", "url": "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us3-review-group/site/\' & $.pub.id & \'/content/index.html"}\' &', + `'{"type": "web-page", "url": "${SITE_BASE}" & $.pub.id & '/index.html"},' &`, + `'{"type": "web-content", "url": "${SITE_BASE}" & $.pub.id & '/content/index.html"},' &`, "']' &", "'}]}]}}' &", "'}'", @@ -1152,10 +1154,10 @@ export async function seedCoarUS3(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Review published and announced: {{ $.pub.title }}", - body: `Review **{{ $.pub.title }}** has been published and sent to the aggregator.\n\nView the pub: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, + body: `Review **{{ $.pub.title }}** has been published and sent to the aggregator.\n\nView the pub: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, }, }, ], @@ -1189,7 +1191,7 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { ReviewCompleted: "dddddddd-0004-4ddd-dddd-dddddddddd14" as StagesId, } - const SITE_BASE = "http://localhost:9000/assets.v7.pubpub.org/sites/coar-us4-repository/site" + const SITE_BASE = `${BASE_SITE_URL}/coar-us4-repository/site/` return seedCommunity( { @@ -1197,7 +1199,7 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { id: communityId, name: "US4: Arcadia Science", slug: "coar-us4-repository", - avatar: `${env.PUBPUB_URL}/demo/croc.png`, + avatar: `${env.PUBSTAR_URL}/demo/croc.png`, }, pubFields: { Title: { schemaName: CoreSchemaType.String }, @@ -1304,9 +1306,9 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Ingest request accepted: {{ $.pub.title }}", - body: "The ingest request **{{ $.pub.title }}** has been accepted.\n\nView: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The ingest request **{{ $.pub.title }}** has been accepted.\n\nView: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, { @@ -1332,9 +1334,9 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Ingest request rejected: {{ $.pub.title }}", - body: "The ingest request **{{ $.pub.title }}** has been rejected.\n\nView: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", + body: "The ingest request **{{ $.pub.title }}** has been rejected.\n\nView: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}", }, }, { @@ -1368,7 +1370,7 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { ], }, resolver: - '$.pub.id = {{ $replace($replace($eval($.pub.values.Payload).object.`as:inReplyTo`, $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBPUB_URL & "/c/" & $.community.slug & "/pub/", "") }}', + '$.pub.id = {{ $replace($replace($eval($.pub.values.Payload).object.`as:inReplyTo`, $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pubs/", ""), $.env.PUBSTAR_URL & "/c/" & $.community.slug & "/pub/", "") }}', actions: [ { action: Action.createPub, @@ -1451,10 +1453,10 @@ export async function seedCoarUS4(communityId?: CommunitiesId) { { action: Action.email, config: { - recipientEmail: "all@pubpub.org", + recipientEmail: "all@pubstar.org", subject: "Site published with new review: {{ $.pub.title }}", - body: `The community site has been updated with a new review.\n\nReview: **{{ $.pub.title }}**\n\nView the pub: {{ $.env.PUBPUB_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, + body: `The community site has been updated with a new review.\n\nReview: **{{ $.pub.title }}**\n\nView the pub: {{ $.env.PUBSTAR_URL }}/c/{{ $.community.slug }}/pub/{{ $.pub.id }}\n\nView the site: ${SITE_BASE}/index.html`, }, }, ], diff --git a/core/prisma/seeds/legacy.ts b/core/prisma/seeds/legacy.ts index 7a3468c2cc..3f8a99c190 100644 --- a/core/prisma/seeds/legacy.ts +++ b/core/prisma/seeds/legacy.ts @@ -1,6 +1,5 @@ import type { CommunitiesId, PubsId } from "db/public" -import { readFile } from "node:fs/promises" import { faker } from "@faker-js/faker" import { CoreSchemaType, MemberRole } from "db/public" @@ -13,7 +12,7 @@ import { usersExisting } from "./users" const abstract = `

The development of AAV capsids for therapeutic gene delivery has exploded in popularity over the past few years. However, humans aren’t the first or only species using viral capsids for gene delivery — wasps evolved this tactic over 100 million years ago. Parasitoid wasps that lay eggs inside arthropod hosts have co-opted ancient viruses for gene delivery to manipulate multiple aspects of the host’s biology, thereby increasing the probability of survival of the wasp larvae

` export const seedLegacy = async (communityId?: CommunitiesId) => { - const poniesText = await readFile(new URL("./ponies.snippet.html", import.meta.url), "utf-8") + const { poniesText } = await import("./ponies.snippet") const articleSeed = (number = 1_000, asRelation = false) => Array.from({ length: number }, (_, idx) => { @@ -377,9 +376,9 @@ export const seedLegacy = async (communityId?: CommunitiesId) => { }, users: { "legacy-user-1": { - email: "legacy@pubpub.org", + email: "legacy@pubstar.org", role: MemberRole.admin, - password: "pubpub-legacy", + password: "pubstar-legacy", }, ...usersExisting, }, diff --git a/core/prisma/seeds/ponies.snippet.html b/core/prisma/seeds/ponies.snippet.html index 67faad2418..153dc062f8 100644 --- a/core/prisma/seeds/ponies.snippet.html +++ b/core/prisma/seeds/ponies.snippet.html @@ -160,7 +160,7 @@

Experimental design

>166±29kg166 \pm 29 kg166 pm 29 kgFabrication of construct >=10μm= 10 \mu m= 10 mu mFabrication of construct >300x300μm300x300 \mu m300x300 mu mFabrication of construct >1300μm1300 \mu m1300 mu mFabrication of construct >c−115 mm\cdot sec^{-1}15 mmcdot sec^{-1}Fabrication of construct >22−24°C22 - 24 \degree \text{C}22 - 24 degree \text{C}Fabrication of construct >30−50%30 - 50\%30 - 50%Fabrication of construct >70%70\%70%Fabrication of construct >l−12.2 g\cdot ml^{-1}2.2 gcdot ml^{-1}Fabrication of construct >α\alphaalphaFabrication of construct >=3.83μm= 3.83 \mu m= 3.83 mu mFabrication of construct >l−10.13 g\cdot ml^{-1}0.13 gcdot ml^{-1}Fabrication of construct >v−140\% w\cdot v^{-1}40% wcdot v^{-1}Fabrication of construct >α\alphaalphaFabrication of construct >0.22μm0.22 \mu m0.22 mu mFabrication of construct >4°C4\degree \text{C}4degree \text{C}Fabrication of construct >=250μm= 250 \mu m= 250 mu mFabrication of construct >700μm700 \mu m700 mu mFabrication of construct >70%70\%70%In Vitro pre-culture >37°C37\degree \text{C}37degree \text{C}In Vitro pre-culture >v−10.2\% w\cdot v^{-1}0.2% wcdot v^{-1}In Vitro pre-culture >v−10.075\% w\cdot v^{-1}0.075% wcdot v^{-1}In Vitro pre-culture >v−110\% v\cdot v^{-1}10% vcdot v^{-1}In Vitro pre-culture >1%1\%1%In Vitro pre-culture >L−1100 U\cdot mL^{-1}100 Ucdot mL^{-1}In Vitro pre-culture >L−1100 \mu g\cdot mL^{-1}100 mu gcdot mL^{-1}In Vitro pre-culture >100μl100 \mu l100 mu lIn Vitro pre-culture >l−1100 ng\cdot ml^{-1}100 ngcdot ml^{-1}Surgical procedure >g−110 \mu g\cdot kg^{-1}10 mu gcdot kg^{-1}Surgical procedure >g−10.1 mg\cdot kg^{-1}0.1 mgcdot kg^{-1}Surgical procedure >g−10.06 mg\cdot kg^{-1}0.06 mgcdot kg^{-1}Surgical procedure >g−12.2 mg\cdot kg^{-1}2.2 mgcdot kg^{-1}Surgical procedure >g−110 \mu g\cdot kg^{-1}10 mu gcdot kg^{-1}Surgical procedure >g−10.5 mg\cdot kg^{-1}0.5 mgcdot kg^{-1}Surgical procedure >g−10.6 mg\cdot kg^{-1}0.6 mgcdot kg^{-1}Surgical procedure >g−10.1 - 0.2 mg\cdot kg^{-1}0.1 - 0.2 mgcdot kg^{-1}Surgical procedure >g−110 - 15 mg\cdot kg^{-1}10 - 15 mgcdot kg^{-1}Surgical procedure >g−120 mg\cdot kg^{-1}20 mgcdot kg^{-1}Surgical procedure >g−10.6 mg\cdot kg^{-1}0.6 mgcdot kg^{-1}Surgical procedure >g−15 mg\cdot kg^{-1}5 mgcdot kg^{-1}Euthanasia and sample harvest >g−10.06mg\cdot kg^{-1}0.06mgcdot kg^{-1}Euthanasia and sample harvest >g−12.2 mg\cdot kg^{-1}2.2 mgcdot kg^{-1}Euthanasia and sample harvest >g−11400 mg\cdot kg^{-1}1400 mgcdot kg^{-1}Biomechanical evaluation >n−10.250 N\cdot min^{-1}0.250 Ncdot min^{-1}Biomechanical evaluation >200μm200 \mu m200 mu mBiomechanical evaluation >10−12%10-12 \%10-12 %Biochemical evaluation >60°C60\degree \text{C}60degree \text{C}Biochemical evaluation >−80°C-80\degree \text{C}-80degree \text{C}Microcomputed tomography >=200μA= 200 \mu A= 200 mu AMicrocomputed tomography >=30μm3= 30 \mu m^{3}= 30 mu m^{3}Histological evaluation >4%4\%4%Histological evaluation >5μm5 \mu m5 mu mHistological evaluation >l−11.083 mg\cdot ml^{-1}1.083 mgcdot ml^{-1}Histological evaluation >l−10.06 mg\cdot ml^{-1}0.06 mgcdot ml^{-1}Histological evaluation >4%4\%4%Histological evaluation >5μm5 \mu m5 mu mStatistical analysis >±\pmpmIn vitro >⋅\cdotcdotIn vitro >g−1199.7 \pm 67.7 \mu g\cdot \mu g^{-1}199.7 pm 67.7 mu gcdot mu g^{-1}In vitro >g−13702 \pm 2111 U\cdot\mu g^{-1}3702 pm 2111 Ucdotmu g^{-1} >g−130.46 \pm 15.95 \mu g\cdot\mu g^{-1}30.46 pm 15.95 mu gcdotmu g^{-1} >g−124.44 \pm 15.31 \mu g\cdot g^{-1}24.44 pm 15.31 mu gcdot g^{-1} >g−179.66 \pm 91.21 \mu g\cdot\mu g^{-1}79.66 pm 91.21 mu gcdotmu g^{-1} >g−1134.21\pm 153.73 \mu g\cdot\mu g^{-1}134.21pm 153.73 mu gcdotmu g^{-1}0.31 \pm 0.13 MPa0.31 pm 0.13 MPa0.42 \pm 0.19 MPa0.42 pm 0.19 MPa1.75 \pm 0.80 MPa1.75 pm 0.80 MPa2.22 \pm 0.48 MPa2.22 pm 0.48 MPa1.86 \pm 0.78 MPa1.86 pm 0.78 MPa2.19 \pm 0.77 MPa2.19 pm 0.77 MPa >6.14%±10.09%6.14\% \pm 10.09\%6.14% pm 10.09% >4.73%±4.93%4.73\% \pm 4.93\%4.73% pm 4.93% >81.38%±15.37%81.38\% \pm 15.37\%81.38% pm 15.37% >74.71%±12.44%74.71\% \pm 12.44\%74.71% pm 12.44% >12.48%±9.75%12.48\% \pm 9.75\%12.48% pm 9.75% >20.56%±10.54%20.56\% \pm 10.54\%20.56% pm 10.54% >79.02±16.18%79.02 \pm 16.18 \%79.02 pm 16.18 % >63.20±13.90%63.20 \pm 13.90 \%63.20 pm 13.90 % + + +

+

+

Take home message

+

+ The collapse of the osteal anchor of these osteochondral implants affected the integration of + the construct with the native tissue and seriously compromised the mechanical stability. This + precluded drawing firm conclusions about the potential for in vivo cartilage repair of + the chondral compartment, consisting of bone morphogenetic protein-9 (BMP-9) stimulated + progenitor cells. Collapse occurred because bone regeneration competent materials, which had + shown success in a different anatomical location, were unable to correctly anchor the implant in + the complex biomechanical environment of the joint. The imperfect dimensional match, originating + from the intrinsic variability of the 3D printing process, had a much bigger influence than in + previous studies. When combined with the brittle nature of the material, the implant was unable + to withstand the complex loading of the knee joint. +

+

Purpose

+

+ The current study aimed at evaluating a cell-laden and a cell-free version of an osteochondral + composite scaffold for cartilage repair that consisted of an in vivo proven osteogenic + bone scaffold for the osteal compartment and made use of a novel interface for the connection of + the chondral and osteal compartments. +

+

Introduction

+

+ Focal cartilage damage is a major challenge in human healthcare since it leads to an increased + risk of developing early osteoarthritis !unsupported node 'citation'!. Most of + the available repair approaches are palliative with limited alleviation time, generating fibrous + tissue with reduced mechanical strength !unsupported node 'citation'!. There + are no effective treatments that can fully restore the anatomical structure and function of + focal cartilage defects. This unmet clinical need drives the ongoing quest for regenerative + medicine and tissue-engineering approaches for articular cartilage repair + !unsupported node 'citation'!. +

+

+ Many new promising technologies + !unsupported node 'citation'!!unsupported node 'citation'! are currently being + developed and tested with the aim of finding an implant that is effective in facilitating + regeneration of cartilage. Given the difficulties associated with the fixation of chondral + constructs in the joint + !unsupported node 'citation'!!unsupported node 'citation'!, an alternative + approach is the use of composite osteochondral constructs composed of distinct osteal and + chondral compartments that can be surgically press-fitted into suitably prepared defects, + thereby avoiding the risk of dislodgement !unsupported node 'citation'!. + However, the latter approach still faces many challenges, including design and optimization of + the osteal compartment to act as an anchor for the overlying chondral compartment, production of + a firm and durable connection between osteal and chondral compartment + !unsupported node 'citation'!, and optimization of the composition and + structure of the chondral compartment + !unsupported node 'citation'!!unsupported node 'citation'!. +

+

+ To address those challenges, biomaterials that hold the potential for facilitating + osteoregeneration within the osteal compartment were recently developed and investigated. First, + 3D printed brushite-based scaffolds have been shown to be effective in promoting new bone growth + after 6 months in an equine model that used the tuber coxae as implantation site + !unsupported node 'citation'!. However, these materials are usually processed + using aggressive acidic treatments, precluding the direct incorporation of cells and/or some + types of polymer during the fabrication phase. Therefore, an apatite-based scaffold that could + harden under physiological conditions into a calcium-deficient hydroxyapatite (CDHA) was + developed. This material had been shown to be effective in a 7-month long + in vivo study, upon implantation in a critical size defect in the tuber coxae of + horses. That study was performed to compare two sophisticated architectures with constant and + gradient pore size, respectively. The material was shown to facilitate excellent new bone + formation, particularly when using the scaffold with constant pore size + !unsupported node 'citation'!. This showed the potential for using such + material as an osteal anchor of a tissue-engineered osteochondral graft. +

+

+ Another major challenge is the connection between the osteal and chondral compartments of the + tissue-engineered osteochondral graft when using cell-friendly materials of strongly different + mechanical characteristics. Recently, a technique for attaching the chondral compartment to the + osteal compartment using melt electrowriting (MEW) was developed + !unsupported node 'citation'!, in which MEW fibers of the chondral compartment + were partially incorporated into the slowly setting apatite-based osteal compartment, thereby + binding the two compartments together. This strategy allows both for optimizing the mechnical + properties of the MEW-reinforced chondral compartment, and for integrating the chondral and + osteal compartments. +

+

+ Regarding the seeding of regeneration-competent cells within the chondral compartment, articular + cartilage-derived progenitor cells have been recently identified and characterized in both + humans and horses as a distinct cell population that has the potential for cartilage repair + !unsupported node 'citation'!!unsupported node 'citation'!. This potential was + further shown to be retained in combination with biomaterials + !unsupported node 'citation'!!unsupported node 'citation'!, making this cell + type a promising candidate for a comprehensive regenerative approach. Additionally, it was + recently discovered that supplementation with BMP-9 during in vitro culture of ACPCs + resulted in higher expression of gene-markers related with hyaline-like extracellular matrix + production, compared to supplementation with transforming growth factor (TGFβ\\beta), a more commonly used growth factor in cartilage tissue engineering + !unsupported node 'citation'!. This observation sparked interest in further + investigation of the potential of BMP-9-stimulated ACPCs for cartilage repair in vivo. +

+

+ The current study aimed at evaluating an osteochondral composite scaffold for cartilage repair. + These constructs were composed of a combination of a previously proven osteogenic CDHA 3D + printed scaffold for the osteal compartment, onto which a chondral compartment composed by MEW + micro-fibrous meshes is tightly anchored. For the chondral compartment, an experimental group in + which the MEW structure was seeded with ACPCs that had been stimulated for 28 days with BMP-9 + before implantation !unsupported node 'citation'!, was compared with an implant + featuring a non-filled, cell-free MEW cartilage scaffold as control. It was hypothesized that 1) + the CDHA scaffold would show comparable performance in the horse when implanted in the + subchondral bone as in the tuber coxae in terms of firmly anchoring in the surrounding tissue + and inducing bone growth; 2) the novel interface would provide a lasting connection between the + osteal and chondral compartments of osteochondral graft; and 3) the engineered chondral + compartment of the osteochondral graft containing the stimulated ACPCs would outperform the + cell-free structures in terms of in vivo cartilage matrix production (specifically, + amount and density of type II collagen and GAGs, with resulting mechanical properties as close + as possible to those of native, healthy cartilage). +

+

Materials and Methods

+

Experimental design

+

+ To assess the performance of integrated 3D printed osteochondral grafts that contained a + cell-laden or a cell-free chondral compartment, the constructs were orthotopically implanted in + a large animal model. Eight Shetland ponies (female, age + 4−124-12 + years, weight + 149−217kg149 - 217 kg + (166±29kg166 pm 29 kg)) were used and samples were implanted in the medial femoral ridge in the stifle joints. + Healing was monitored for 6 months, after which the animals were humanely euthanized. The study + was approved by the ethical and animal welfare body of the Utrecht University (Approval nr. + AVD108002015307 WP23). +

+

+ Ponies were housed in individual boxes and fed a limited ration of concentrates together with + hay for maintenance and free access to water. Quantitative gait analysis and radiographic + examination of the stifle joints were performed before surgery for baseline values. + Post-operatively, the animals were kept stabled for 6 weeks with daily monitoring of vital + signs, lameness checks at walk, and examination of the operated joints for swelling or other + signs of inflammation. In week 5 and 6, they were hand-walked for 10 minutes twice daily and + from week 7 on, they were kept at pasture. Quantitative gait analysis and radiographic exams + were performed at 3 weeks, 3 months, and 6 months post-operatively. After 6 months, ponies were + humanely euthanized for harvesting samples for both quantitative and qualitative analyses. The + timeline of the experiment is represented in Figure 1. +

+
+ +
+ Flow chart representing timeline of the experiment including health monitoring at each phase + of the experiment. +
+
+

Fabrication of construct

+

+ Microfiber meshes were produced from medical-grade PCL (Purasorb® PC 12 Corbion PURAC, The + Netherlands) by using MEW technology as previously described + !unsupported node 'citation'!. The meshes were produced by horizontally + patterning the microfiber (diameter + =10μm= 10 mu m) to form continuously uniform square spacing (300x300μm300x300 mu m) and vertically stacking the same pattern until reaching + 1300μm1300 mu m + in total thickness. This structure was achieved by printing with a temperature of 90C, a + pressure of 1.25 bar, voltage of 10 kV, and collector velocity of + 15mm⋅sec−115 mmcdot sec^{-1}. Additionally, printing was performed at ambient temperature (22−24°C22 - 24 degree \\text{C}) with a humidity between + 30−50%30 - 50%. Subsequently, PCL microfiber meshes were hydrolyzed by soaking them in sodium hydroxide (1M + NaOH) for 15 minutes and washed in Milli Q water for 10 minutes 4 times. Finally, sterilization + was carried out by immersion of the mesh in + 70%70% + ethanol for + 1515 + minutes, followed by air-drying in a sterile cabinet until use. +

+

+ Printable calcium phosphate (PCaP) paste was prepared as earlier described + !unsupported node 'citation'!. In short, + 2.2g⋅ml−12.2 gcdot ml^{-1} + of alpha-tri calcium phosphate (αalpha-TCP, average particle size + =3.83μm= 3.83 mu m, Cambioceramics, Leiden, the Netherlands) and + 0.13g⋅ml−10.13 gcdot ml^{-1} + of nano-hydroxyapatite (nano-HA, particle size + =200nm=200 nm, Ca5(OH)(PO4)3, Sigma-Aldrich) were mixed with + 40%w⋅v−140% wcdot v^{-1} + poloxamer solution (Pluronic® F-127, Sigma-Aldrich). + αalpha-TCP and nano-HA powder were disinfected with UV-light for 1 hour before mixing. The poloxamer + solution was disinfected by filtration through a + 0.22μm0.22 mu m + sterile filter (Millex®-GS). This paste was loaded to a cartridge and kept at + 4°C4degree \\text{C} + until use. +

+

+ Osteochondral constructs were produced by combining the PCL microfiber mesh and the PCaP paste + to form the reinforcement of the chondral compartment and the biomimetic bone compartment, + respectively. Fabrication was performed by directly depositing the PCaP paste (approximated + strand diameter + =250μm= 250 mu m) onto the hydrolyzed MEW mesh (Figure 2). Eighty percent of the + mesh thickness was set as the initial height for depositing the first of non-macroporous PCaP + layer, as this proved to be the height that did not damage the mesh structure and ensured an + optimal integration between the bone compartment and the chondral compartment. The first two + layers of PCaP were deposited without macro-spacing, to mimic the subchondral bone plate, and + followed by layers with designed macro-spacing of + 700μm700 mu m + to mimic the cancellous bone section (diameter + =6mm= 6 mm, height + =5mm= 5 mm). +

+

+ After finishing the fabrication process, the osteochondral constructs were allowed to set at 37C + under saturated relative humidity to form a solid, biomimetic bone compartment through + conversion of the PCaP composite to CDHA. Finally, the osteochondral constructs were disinfected + in + 70%70% + ethanol and exposed to UV-light for + 11 + hour, prior to seeding of cells. +

+

In Vitro pre-culture

+

+ Allogeneic articular cartilage progenitor cells (ACPCs) were obtained as previously described + !unsupported node 'citation'!!unsupported node 'citation'! from animals that + were euthanized at the Utrecht University Veterinary Hospital for causes unrelated to disease or + impairment of the musculoskeletal system and whose remains were donated for research purposes. + Briefly, hyaline cartilage was collected in a sterile fashion, minced, and digested at + 37°C37degree \\text{C} + with + 0.2%w⋅v−10.2% wcdot v^{-1} + pronase solution for + 22 + hours, followed by + 1212 + hours in + 0.075%w⋅v−10.075% wcdot v^{-1} + collagenase solution. ACPCs were then selected using a fibronectin adhesion assay + !unsupported node 'citation'!. Cells were expanded in culture and stored in + liquid nitrogen until further use. After thawing, cells were expanded until passage 3 prior to + their use for the experiment. +

+

+ The constructs made of the combined CDHA and MEW meshes were disinfected in ethanol and exposed + to UV-light for 1 hour as mentioned above. To avoid any pH changes that might affect the cells, + the constructs were subsequently washed 3 times for 10 minutes with PBS and then immersed for 1 + week in cell culture medium consisting of Dulbecco’s Modified Eagle Medium/Nutrient Mixture F-12 + (DMEM/F-12, 11320033, Gibco, The Netherlands) supplemented with + 10%v⋅v−110% vcdot v^{-1} + heat-inactivated fetal calf serum (FCS, Gibco, The Netherlands), + 0.2mM0.2 mM + L-ascorbic acid 2-phosphate (Sigma), + 1%1% + MEM Non-Essential Amino Acids Solution (11140035, Gibco, The Netherlands) and + 100U⋅mL−1100 Ucdot mL^{-1} + penicillin with + 100μg⋅mL−1100 mu gcdot mL^{-1} + streptomycin (Life Technologies, The Netherlands). Media were refreshed every 2-3 days. +

+

+ On the day of seeding, medium was refreshed 2 hours before seeding and scaffolds were placed + inside a custom-made polydimethylsiloxane (PDMS) ring (Figure 2) that + prevented overflow of the cell suspension from the cartilage compartment to the bone scaffold. + Ten million cells were suspended in + 100μl100 mu l + of medium and seeded on top of the constructs. The cell suspension was left to settle at the + bottom of the cartilage part for 30 minutes. Afterwards, + 2ml2 ml + of cartilage medium supplemented with + 100ng⋅ml−1100 ngcdot ml^{-1} + of BMP-9 (PeproTech, The Netherlands) was carefully added to the well. The seeded constructs + were cultured for 4 weeks prior to implantation, refreshing the medium 3 times a week. +

+
+ +
+ Schematic picture representing the fabrication process of the tissue engineered + osteochondral constructs. + Fabrication techniques and processes for forming osteochondral graft (A), Material + composition of osteochondral graft and process during in vitro preculture (B). +
+
+

Surgical procedure

+

+ Ponies were premedicated with detomidine (intravenous (IV), + 10μg⋅kg−110 mu gcdot kg^{-1}) and morphine (IV, + 0.1mg⋅kg−10.1 mgcdot kg^{-1}) and anesthesia was induced with midazolam (IV, + 0.06mg⋅kg−10.06 mgcdot kg^{-1}) and ketamine (IV, + 2.2mg⋅kg−12.2 mgcdot kg^{-1}). Anesthesia was maintained with isoflurane in oxygen together with continuous rate infusion + of detomidine (IV, + 10μg⋅kg−110 mu gcdot kg^{-1}/h) and ketamine (IV, + 0.5mg⋅kg−10.5 mgcdot kg^{-1}/h). Meloxicam (IV, + 0.6mg⋅kg−10.6 mgcdot kg^{-1}), morphine (epidural injection, + 0.1−0.2mg⋅kg−10.1 - 0.2 mgcdot kg^{-1}) and ampicillin (IV, + 10−15mg⋅kg−110 - 15 mgcdot kg^{-1}) were administered pre-operatively as analgesic medication and antibacterial preventative + therapy, respectively. +

+

+ The medial femoral ridge of the stifle joint was exposed by arthrotomy and an osteochondral + lesion (diameter + =6mm= 6 mm, depth + =6mm= 6 mm) was surgically created using a power drill. The surgical area was flushed by saline for + cooling and removal of debris. Cell-laden constructs were implanted press-fit in a randomly + chosen hind limb, with the cell-free control being implanted in the contralateral limb. After + closing the arthrotomy wound in four layers in routine fashion, procaine penicillin was + administered (Procapen, intramuscular (IM), + 20mg⋅kg−120 mgcdot kg^{-1}). Post-operatively, nonsteroidal anti-inflammatory medication (metacam, per os (PO), SID, + 0.6mg⋅kg−10.6 mgcdot kg^{-1}) was administered for 5 days and opioids (tramadol, PO, BID, + 5mg⋅kg−15 mgcdot kg^{-1}) were administered for + 22 + days. +

+

Gait analysis

+

+ The ponies were trained on a treadmill prior to the study using a standard protocol for + treadmill habituation. Twenty-eight spherical reflective markers with a diameter of + 24mm24 mm + (topline) and + 19mm19 mm + (elsewhere) were attached with double-sided tape and second glue to anatomical landmarks (Figure + 3). Kinematic data were collected on a treadmill (Mustang, + Fahrwangen, Switzerland) at trot using six infrared optical motion capture cameras (ProReflex, + Qualisys, Gothenburg, Sweden) recording at a frame rate of + 200Hz200 Hz + for + 3030 + seconds at each session to obtain a sufficient number of strides. +

+

+ To process the data, the reconstruction of three-dimensional coordinates of each marker was + automatically calculated by Q-Track software (Qtrack, Qualisys, Gothenburg, Sweden). Each marker + was identified and labelled using an automated model (AIM model) and manual tracking. Raw data + of the designated markers were exported to Matlab (version 2018a, Niantics, California) for + further analysis using custom written scripts. For each stride, two symmetry parameters were + calculated using the vertical displacement of the head and pelvis (tubera sacrale) markers. For + each stride, the differences between the two vertical displacement minima of the head + (MinDiffhead) and pelvis (MinDiffpelvis) were calculated. Using the markers, limb-segments were + formed and angles between these limb-segments were calculated. The difference between the + maximal and minimal angle was defined as the range of motion (ROM) of a joint. For each + timepoint, the mean value of all strides for each parameter was calculated. +

+
+ +
+ Schematic picture representing location of the markers for gait analysis. +
+
+

Radiographic examination

+

+ Stifles were radiographed in 3 projections: lateromedial, craniolateral-caudomedial oblique and + caudo-cranial projection using standard machine settings before surgery (baseline), at 3 weeks + postoperatively and at 6 months, just before euthanasia. +

+

Euthanasia and sample harvest

+

+ After 6 months, animals were euthanized by induction with Midazolam (IV, + 0.06mg⋅kg−10.06mgcdot kg^{-1} + body weight) with ketamine IV, (2.2mg⋅kg−12.2 mgcdot kg^{-1} + body weight) and subsequent administration of sodium pentobarbital (IV, + 1400mg⋅kg−11400 mgcdot kg^{-1} + body weight). Next, the stifle joint was exposed, and gross assessment of the medial trochlear + ridge was performed, focusing on the degree of filling of the defect, the integration of repair + tissue with the surrounding native tissue and the surface quality of the repair tissue. + Subsequently, the entire osteochondral area containing the constructs was harvested for further + analyses with the aid of a surgical bone saw. Harvested tissues were initially kept in + sterilized PBS for micro-computed tomography (micro-CT) scanning, biomechanical analyses and for + collecting tissue from the chondral compartment of the implant for biochemical analyses. After + this, all tissues were fixed in 4% formaldehyde for subsequent histological processing. +

+

Biomechanical evaluation

+

+ The compressive properties of the chondral compartment of the defect site, the adjacent + surrounding native cartilage and the more distant surrounding native cartilage (5−10mm5 - 10 mm + from the boundary of the defect) (N=7N=7 + for cell-laden constructs and + N=7N=7 + for cell-free constructs) were evaluated with a dynamic mechanical analyzer (DMA, DMA Q800, TA + instrument) equipped with a custom-size compressing probe (diameter + =2mm= 2 mm). A ramp force of + 0.250N⋅min−10.250 Ncdot min^{-1} + was applied until reaching + 2.0N2.0 N, to limit the deformation of sample to values below + 200μm200 mu m. Compression modulus was calculated as the slope of the stress-strain curve in the range + between + 10−12%10-12 % + strain. +

+

Biochemical evaluation

+

+ Firstly, biochemical analyses were performed on supplemental pre-implantation constructs + (N=3N=3) that had been prepared in the same batch as the constructs that were later implanted. The + chondral compartments of 28-day cultured constructs were removed and freeze-dried. Next, dry + samples were digested in papain (Sigma Aldrich) at + 60°C60degree \\text{C} + overnight. DNA, sulphated glycosaminoglycan (sGAG), and alkaline phosphatase (ALP) content were + quantified by performing the Quan-iT-Picogreen-dsDNA-kit assay (Molecular Probes, Invitrogen, + Carlsbad, USA), the dimethylmethylene blue assay (DMMB, Sigma-Aldrich, The Netherlands) and the + p-nitrophenyl phosphate assay (SIGMAFAST, Sigma-Aldrich), respectively. +

+

+ Secondly, tissue fractions that were collected from the chondral compartments of harvested + implants (N=6N=6 + for cell-laden constructs, + N=7N=7 + for cell-free constructs) were kept at + −80°C-80degree \\text{C}, followed by lyophilization. Collagen content was quantified using an hydroxyproline assay + (L-Hydroxyproline, Merck KGaA), and the sGAG and DNA quantification was performed as described + above. +

+

Microcomputed tomography

+

+ Microcomputed tomography was employed for the quantitative analysis of the bone compartments + from the harvested osteochondral lesions (N=7N=7 + for cell-laden constructs, + N=7N=7 + for cell-free constructs). Six freshly made osteochondral grafts were scanned in a micro-CT + scanner (Quantum FX-Perkin Elmer) to quantify the initial volume of PCaP material, + pre-operatively. The post-mortem harvested tissue containing the defect area and the surrounding + native tissue were similarly scanned (voltage + =90kV= 90 kV, current + =200μA= 200 mu A, voxel size + =30μm3= 30 mu m^{3} + and total scanning time + =3minutes= 3 minutes). Subsequently, the 3D-reconstructed images were processed and analyzed using image J software + !unsupported node 'citation'! and Bone J plugin + !unsupported node 'citation'!. Two-dimensional regions of interest (ROIs) were + selected in an axial plane at the boundary between the defect and the surrounding native tissue + and interpolated to form a three-dimensional volume of interest (VOI). Thresholding was + performed to separately select areas of ceramics and newly formed bone respectively for further + calculation. Then, the percentages of mineralized newly formed bone, of non-mineralized tissue + and of remaining ceramics, including the percentage of ceramics volume loss, were quantified. +

+

Histological evaluation

+

+ Firstly, supplemental pre-implantation constructs (N=3N=3) that had been prepared in the same batch as the ones that later were implanted were fixed in + 4%4% + formaldehyde. After decalcification in + 0.5M0.5M + Ethylenediaminetetraacetic acid (EDTA) disodium salt (pH=8pH = 8) for + 11 + day, tissues were dehydrated with graded ethanol series, cleared in xylene, and embedded in + paraffin. Paraffin embedded tissues were sliced to + 5μm5 mu m + sections. Histochemical evaluation of GAG was done by safranin-O / fast green staining. Type I + collagen (primary antibody: monoclonal antibody EPR7785, + 1.083mg⋅ml−11.083 mgcdot ml^{-1}, Abcam) and type II collagen (primary antibody: monoclonal antibody II-II6B3, + 0.06mg⋅ml−10.06 mgcdot ml^{-1}, DSHB) were visualized by immunohistochemistry. +

+

+ The tissues that were harvested after + 66 + months (N=7N=7 + for cell-laden constructs, + N=7N=7 + for cell-free constructs) were kept in + 4%4% + formaldehyde and then decalcified in + 0.5M0.5M + EDTA disodium salt (pH=8pH = 8) for + 2424 + weeks. Decalcified tissues were cut into two halves before processing to enable visual + inspection of the center of the lesion. Tissues were dehydrated with graded ethanol series, + cleared in xylene and finally embedded in paraffin. Paraffin embedded tissues were sliced to + 5μm5 mu m + sections. For assessment of morphology and cell distribution, hematoxylin-eosin staining + (Mayer’s haematoxylin, Merck 109249 and eosin, Merck 115935) was performed. GAG and collagen + alignment were assessed after safranin-O / fast green and picrosirius red staining, + respectively. Types I collagen and type II collagen were visualized by immunohistochemistry, as + described above. For immunohistochemistry, all samples were treated according to previously + published protocols !unsupported node 'citation'!. Stained histological slides + were imaged using a light microscope (Olympus BX51, Olympus Nederland B.V.), equipped with a + digital camera (Olympus DP73, Olympus Nederland B.V.). To observe the picrosirius red stained + slides, a polarizer was also mounted to the light microscope. +

+

Statistical analysis

+

+ Normality of distribution of the data was assessed from skewness, kurtosis, and Q-Q plots. + Results were reported as mean + ±pm + standard deviation. Wilcoxon signed rank tests were used to analyze the biochemical, + biomechanical, and micro-CT data. Statistical significance was set at + p=0.05p = 0.05. All tests were performed using Matlab (version R2018b, The MathWorks, Inc.). +

+

+ To evaluate the gait parameters, stride-level data were analyzed with R software (version 3.6.0, + R Core Team, 2019), using package NLME (version 3.1-137) for mixed modelling. Dependent + variables were investigated for normality using normal probability plotting and examining for + skewness and kurtosis. If not normally distributed, data were transformed to permit linear mixed + modeling. The random effect was subject and timepoint was the fixed effect. Significance was set + at p < 0.05 and p-values were corrected using the false discovery rate method. Residual plots + were checked for heteroscedasticity versus the outcome, as well as for normality in Q-Q plots. +

+

Results

+

In vitro

+

+ After 4 weeks of preculture, macroscopic characterization of tissue formation and hyaline-like + extracellular matrix production were assessed both quantitatively and qualitatively within the + chondral compartment of the cell-laden osteochondral constructs. The BMP-9 stimulated ACPCs + meant to colonize the MEW scaffolds formed neo-tissue that had grown into a disc shape after 3 + weeks of culture. During the 4th week of culture, outgrowth from the MEW meshes was + observed (Figure 4A) from this construct. Cell-free constructs did + not change after immersion in growth factor-free medium for 4 weeks (Figure + 4B). Biochemical analyses of the chondral compartment of the + cell-laden constructs were performed to quantify matrix production of stimulated cells toward + chondrogenic lineage and osteogenic lineage, which revealed the presence of GAGs + (GAGs⋅cdot + DNA-1 was + 199.7±67.7μg⋅μg−1199.7 pm 67.7 mu gcdot mu g^{-1}) and ALP activity (ALP·DNA-1 was + 3702±2111U⋅μg−13702 pm 2111 Ucdotmu g^{-1}), respectively. Safranin-O staining and type II collagen immunohistochemistry were also + performed to visualize hyaline-liked matrix production from stimulated cells, which revealed + abundant deposition of GAGs and type II collagen within the constructs after 3 weeks of + in vitro culture (Figure 4C), showing that the chondral + compartments of the constructs (meant for subsequent implantation) were filled with a hyaline + cartilage-like tissue. No preferential alignment of the collagen fibers could be observed. +

+
+ +
+ Representative pictures of cell-laden and cell-free osteochondral constructs at the + time of implantation. + Cell-laden (A) and cell-free constructs (B) at the time of implantation. Positive safranin-O + staining indicating the presence of glycosaminoglycans (pink = positive), positive type II + collagen (brown = positive) and negative type I collagen (brown = positive) + immunohistochemistry were observed in the chondral compartment of the cell-laden constructs + before implantation (C). +
+
+

Evaluation during surgical implantation

+

+ Both cell-laden and cell-free constructs were press-fit implanted into the surgically created + defect sites. During this procedure, the slightly irregular outer edge of the osteal part of the + construct hampered easy sliding of the construct down into the defect and some fragmentations of + the edges of the bioceramic scaffold was observed during the procedure. This was similar for the + cell-laden and cell-free constructs, which had identical osteal parts (Figure + 5). Further, of some cell-laden constructs, the surface of the + chondral compartment was not level over the entire circumference with the surrounding native + cartilage after press-fitting into the defect site. +

+
+ +
+ Representative pictures show white fragments of broken ceramic after press-fitting + cell-laden and cell-free osteochondral constructs into the defect. + Black arrows indicate the position of some visible bioceramic fragments. White arrows + indicate protrusion of the chondral compartment +
+
+

Post-operative clinical monitoring

+

+ After surgical implantation, the animals were checked clinically for physical appearance and + vital signs on a daily basis. All ponies recovered well from anesthesia after surgery and passed + uneventfully through the rehabilitation period without any abnormalities in body temperature or + behavior, with good weight-bearing on all operated limbs and no clinical signs of lameness + during the entire period, with the exception of a single pony that developed severe lameness at + 10 weeks after surgery. This pony was treated with anti-inflammatory medication and examined + radiographically, which revealed extensive osteolysis around the created lesion. Because of + persistent discomfort, the pony was euthanized at 12 weeks after surgery. Therefore, it was + excluded from all analyses. +

+

Gait analysis

+

+ Objective gait analysis was used to check for lameness or other signs of dysfunction of the + musculoskeletal system. Objective data retrieved before implantation and at the end of the + experiment were assessed for relevant parameters, including symmetry parameters and limb + parameters. +

+

Symmetry parameters

+

+ Front and hind limb lameness were analyzed through evaluation of the symmetry parameters of the + head (MinDiff Head (Figure 6A)) and of the pelvis (MinDiff Pelvis + (Figure 6B)). These values reflect the differences in minimal + vertical displacement with a negative MinDiff indicating a left-sided asymmetry and a positive + MinDiff a right-sided asymmetry. In the treated ponies (except for the case referred to above + that was euthanized), for both the head and the pelvis, there was no clear pattern in the + direction of the asymmetries between baseline and endpoint and those differences between + baseline and endpoint were minimal and statistically not significant. Therefore, symmetry + measures could not discriminate between cell-laden and cell-free constructs. Further, there was + also no clear effect of timepoint on pelvis roll and pelvis yaw range of motion (Supplementary + Figure 12), however, pelvis pitch range of motion (ROM) (Figure + 6C) decreased for all subjects with almost 20% over time + (Supplementary Table 1). +

+

Limb parameters, effects of time

+

+ There was a significant effect of time for the height the toe was lifted from the surface during + the swing phase of the limb that decreased significantly in the cell-free treated limbs, but not + in the limbs treated with cell-laden constructs (Supplementary Table + 1). The only other significant effect of time was a decrease in the + extension of the metacarpophalangeal joint of the forelimb ipsilateral to the hind limb that had + been treated with cell-laden constructs, indicating unloading of that forelimb (Supplementary + Table 1). +

+

+ Limb parameters, differences between cell-laden and cell-free at endpoint +

+

+ There were no significant differences between any of the cell-laden and cell-free limb + parameters at the end of the experiment. Results from the linear mixed model are shown in + Supplementary Table 2. +

+
+ +
+ Gait analysis: Symmetry parameters. Symmetry data of the head (A) and pelvis (B) show + no consistent differences over time. However, pelvis pitch decreased consistently in all + individuals (C). +
+
+

Radiographic examination

+

+ Healing progression within the osteal compartment of the implanted osteochondral constructs was + followed up non-invasively through radiographic examination. On the radiographs taken at + baseline, 3, and 6 months, no obvious abnormalities in term of the architecture of the + surrounding native tissue were detected, other than the defects that had been created. This was + with the exception of the pony that developed severe lameness. In that animal, severe osteolysis + was noted at the implantation site 3 months after the implantation (Supplementary Figure + 13). +

+

+ Post-mortem macroscopic evaluation of the repair tissue +

+

+ Macroscopic characteristics, for instance, color, appearance, and filling level of the lesion, + were observed and documented before harvesting tissue sample for further analyses. After 6 + months, macroscopic evaluation revealed that the defects were filled with repair tissue that in + all cases did not fill the entire defect and remained lower than the level of the surrounding + native cartilage in both cell-laden and cell-free treatments (Figure + 7A). The color of the repair tissue was variable (from reddish, to + yellow and translucent) within the different treatments (Figure 7B). + In some cases, ceramic fragments could be observed within the repair tissue of the chondral + compartment. +

+
+ +
+ Macroscopic appearance of the repair tissue and surrounding native tissue in all + individual animals at euthanasia. + Macroscopic appearance of the defect site and surrounding femoral ridge (A). Close-ups of + macroscopic appearance at the defect site (B). +
+
+

+ Biochemical analyses of repair tissue within the chondral compartment +

+

+ The deposition of GAGs and collagen, the two main elements that compose cartilage extracellular + matrix, were quantified within the chondral compartment of the osteochondral graft 6 months + after implantation. There were no significant differences in either GAGs (cell-laden: + 30.46±15.95μg⋅μg−130.46 pm 15.95 mu gcdotmu g^{-1}, cell-free: + 24.44±15.31μg⋅g−124.44 pm 15.31 mu gcdot g^{-1}) or collagen expressed per DNA (cell-laden: + 79.66±91.21μg⋅μg−179.66 pm 91.21 mu gcdotmu g^{-1}, cell-free: + 134.21±153.73μg⋅μg−1134.21pm 153.73 mu gcdotmu g^{-1}) between the chondral compartments of the cell-laden and cell-free constructs (Figure + 8A, 8B and Supplementary Figure 14). However, + all values were substantially lower than those from native cartilage (Figure + 8, grey dotted line) that was harvested distantly from the defect + site. +

+

!unsupported node 'iframe'!

+
+ +
+ Biochemical analysis from chondral compartment at the defect site after an implantation + for 6 months. + Quantitative analysis of GAG·DNA-1 between cell-laden and cell-free treatments + (A). Quantitative analysis of collagen⋅ DNA-1 between cell-laden and cell-free + treatments (B) (x = mean). Grey dotted line indicates level in native cartilage. +
+
+

+ Biomechanical properties of the repair tissue within the chondral compartment +

+

+ Compressive strength of the chondral compartment was assessed and compared in three different + locations: the defect site, adjacent surrounding native tissue, and distant surrounding native + tissue (Figure 9A), the latter two as control measurements from + healthy cartilage tissue. There were no significant differences in the Young’s modulus of the + chondral compartment between cell-laden (0.31±0.13MPa0.31 pm 0.13 MPa) and cell-free (0.42±0.19MPa0.42 pm 0.19 MPa) constructs (Figure 9B). This was also true for two sites of the + native cartilage, one close to the border of the defect (cell-laden: + 1.75±0.80MPa1.75 pm 0.80 MPa, cell-free: + 2.22±0.48MPa2.22 pm 0.48 MPa) and one at + 5−10mm5 - 10 mm + from the defect boundary (cell-laden: + 1.86±0.78MPa1.86 pm 0.78 MPa, cell-free: + 2.19±0.77MPa2.19 pm 0.77 MPa) (Figure 9C, 9D). However, the compression modulus of the native + tissue was substantially higher (approximately + 5−65-6-fold) than inside the chondral compartment of the implant. +

+
+ +
+ Compression modulus of the chondral compartment at the defect site and surrounding + native cartilage of harvested samples 6 months after implantation. + Schematic picture demonstrating locations where mechanical properties were analyzed (A). + Compression modulus of the chondral compartment of cell-laden and cell-free constructs at 6 + months (B) and at two sites of the native cartilage, close to the border of the defect (C) + and further away (D). (x = mean). +
+
+

+ Micro-CT evaluation of repair tissue within the osteal compartment +

+

+ Bone healing and integration after was assessed through micro-CT scanning 6 months after + implantation. Micro-CT images showed significant bone loss surrounding the implant in both the + cell-laden and the cell-free groups, which could be visualized as black areas between the porous + bioceramic structure (white) and the surrounding native bone (grey). However, mineralized bone + formation could be visualized in some scaffolds from both groups with an integration to + neighboring native bone (Figure 10A). Statistically, there were no + significant differences in mineralized bone formation (cell-laden: + 6.14%±10.09%6.14% pm 10.09%, cell-free: + 4.73%±4.93%4.73% pm 4.93%) and non-mineralized tissue (cell-laden: + 81.38%±15.37%81.38% pm 15.37%, cell-free: + 74.71%±12.44%74.71% pm 12.44%). However, there was a significant difference in the amount of remaining ceramics between the + two groups (cell-laden: + 12.48%±9.75%12.48% pm 9.75%, cell-free: + 20.56%±10.54%20.56% pm 10.54%(p=0.0313p = 0.0313)) (Figure 10B). In line with this, there was a difference in the + degradation of ceramics in the cell-laden construct versus the cell-free constructs (cell-laden: + 79.02±16.18%79.02 pm 16.18 %, cell-free: + 63.20±13.90%63.20 pm 13.90 %(p=0.0313p = 0.0313)) (Figure 10C). +

+
+ +
+ Representative micro-CT images from the middle of the sagittal plane of the constructs + and quantification from 3D-reconstruction of micro-CT. + Representative micro-CT images from the middle of the sagittal plane of the constructs + (white = ceramics, grey = mineralized tissue, black = non-mineralized tissue) (A). + Quantitative analysis from micro-CT reconstruction showing percentage of mineralized bone + formation, non-mineralized tissue, and remaining ceramics (B). The volume loss of ceramics + was slightly higher in the cell-laden constructs compared to the cell-free ones (C). +
+
+

+ Histological evaluation of the osteochondral repair tissue +

+

+ Histological slides were assessed to identify the composition of the repair tissue matrix + deposited within the defect site. In the chondral compartment, the defect sites of both + cell-laden and cell-free structures were filled with fibrous repair tissue with degenerated and + necrotic superficial surface with minimal inflammatory reaction, as revealed by H&E and + safranin-O staining (Figure 11, Supplementary Figure + 15, Supplementary Figure 17, and Supplementary + Figure 18). Integration at the boundary of the defect between chondral + repair tissue and surrounding native cartilage was observed in both groups. The production of + GAGs, type II collagen, and type I collagen was very limited in the repair tissue in both groups + (Figure 11). The organization of the collagen fibrils in both groups + seemed random, without any hierarchical pattern that could be identified by polarized light + imaging of picrosirius red staining. Additionally, the special distribution of PCL-microfibers, + which had disappeared because of the xylene treatment during sample preparation, was still + traceable within the chondral compartment of both groups (1 out of 7 for cell-laden and 5 out of + 7 for cell-free structure). +

+

+ In the bone compartment, there was positive staining for type I collagen in some scaffolds from + both groups at places where there were islands of new mineralized bone formation. There were + multifocal coalescing spots of inflammatory reaction characterized by macrophages, + multinucleated giant cells, lymphocytes, eosinophils, and plasma cells (Supplementary Figure + 16, Supplementary Figure 17, Supplementary + Figure 18). +

+
+ +
+ Representative histological images from the center part of cell-laden and cell-free + structures after implantation for 6 months. + Safranin-O/fast green (red color = positive) (A, E); Collagen type II (brown color = + positive) (B, F); Picrosirius-red (C, G); collagen type I (brown color = positive) (D, H) of + cell-laden (A-D) and cell-free structures (Scale bar = 1mm). +
+
+

Discussion

+

+ This study aimed to evaluate the efficacy of an engineered osteochondral composite scaffold that + was fabricated by combining a proven osteogenic CDHA scaffold for the osteal compartment with a + novel interface for the connection between the chondral and osteal compartments. For the + chondral compartment, BMP-9 stimulated cell-laden and cell-free constructs were compared. The + cell-laden constructs contained in vitro formed tissue that was rich in GAGs and type + II collagen, obtained by seeding Articular Cartilage Progenitor Cells (ACPCs) and stimulating + them with BMP-9 for 4 weeks prior to implantation. After implantation in an equine osteochondral + defect for 6 months, there was poor chondral repair tissue in both the cell-laden and cell-free + implants. The repair tissue was akin to fibrocartilage and was characterized by the presence of + fibrous tissue with low content of GAGs and type II collagen and a degenerated surface. The CDHA + scaffold had failed to act as an osteal anchor, as evidenced by the radiographical images + showing misalignment and partial collapse of the CDHA construct, the presence of CDHA fragments + within the defect and in the surrounding tissues, and a limited volume of newly formed calcified + bone in the pores of the osteal anchor. +

+

+ In the quest for a method to achieve satisfactory and durable repair of articular cartilage, + several osteochondral grafts that incorporate cells and that were manipulated to optimize + biochemical and biomechanical properties, have been investigated in the past decades + !unsupported node 'citation'!. Articular Cartilage Progenitor Cells have become + a promising cell source due to their ability to retain their chondrogenicity after their + expansion for several passages !unsupported node 'citation'!. Recently, growth + factor BMP-9 was shown to be a potent stimulator of chondrogenic differentiation of this cell + type in vitro !unsupported node 'citation'!. This warranted further + investigations to evaluate the use of BMP-9 stimulated ACPCs for cartilage repair + in vivo. Indeed, the cell-laden chondral compartment showed a high presence of + neo-cartilage extracellular matrix production after pre-culture at the time of implantation, yet + the average GAG content decreased approximately 6.5-fold during the + in vivo implantation period. The GAG content of cell-laden constructs in fact decreased + to the level of the cell-free constructs, suggesting loss or disintegration of the + in vitro formed tissue. Which factor initiated this loss of in vitro formed + tissue remains unclear. A previous study from + !unsupported node 'citation'! demonstrated superior results in using ACPCs for + cartilage repair in an equine model, in comparison with mesenchymal stem cells. However, due to + the use of different materials and cell culture protocols, it is impossible to directly compare + those results with the ones from the current study. Several factors might have been involved in + the deterioration of the chondral compartment in this study, most prominently mechanical + stresses due to the partial failure of the osteal basis and the resulting poor osteointegration + !unsupported node 'citation'!. +

+

+ The nature of the osteal anchor is an important factor when developing tissue-engineered + osteochondral implants. Much work has been done on the development of several types of bone + grafts and many of these are routinely used in clinical settings + !unsupported node 'citation'!, so of the various elements of an osteochondral + implant, the bone part is seemingly the least difficult one. However, the relationship between + the osteal anchor and the quantity and quality of the repair tissue in the chondral compartment + has been the subject of debate !unsupported node 'citation'! and it is still + unclear what osteal anchor would form the best base for facilitating cartilage repair. The exact + same bioceramic material tested in this long-term, orthotopic equine study, had previously been + shown to successfully guide osteoregeneration in the same species, when implanted in the tuber + coxae, an anatomical locus less subject to intense mechanical loads + !unsupported node 'citation'!. Additionally, this previous study also focused + on comparing different pore architecture within the 3D printed bone scaffolds. The scaffold + architecture that led to the highest rate of new bone formation, consisting of a constant pore + size across the sample, was selected for the present study, with the goal of maximizing neo-bone + repair !unsupported node 'citation'!. However, there are two major differences + with the use of the material in the current study. First, in the previous study the material was + implanted in the tuber coxae, which is an orthotopic area but not representative of the + intra-articular environment. Second, in the former study the implant was surrounded by a + cylindrical case made of PCL that served to prevent bone ingrowth from the sides. Without such a + shell of the mechanically deformable PCL in the current study, the surgeon encountered + difficulties during the surgical placement of the implants, provoked by the non-resilience and + brittleness of the CaP-based material, combined with some deviation from an ideal cylindrical + shape of the CDHA implant. This resulted in fragments breaking off from the bioceramic osteal + anchor. In fact, although inadvertently and as a side-effect, this problem of fragment formation + was avoided in the former study when using PCL to encase. Polycaprolactone is deformable + material, and the encasing will have facilitated the sliding of the ceramic implant into the + defect. The duration of both studies was not identical (7 months in the earlier study, 6 months + in the current), which makes direct comparisons between the two impossible. However, there were + clear histological differences with many more multifocal to coalescing inflammatory reactions in + both cell-laden and cell-free implants in the current study, compared to the earlier study, in + which there were very few inflammatory cells visible. This difference is likely due to the + chronic irritation caused by fragments of material and to instability resulting from the + imperfect fit of scaffold within the defect in the current study. +

+

+ Some scaffolds from the current study collapsed and showed misalignment of the CDHA structure + within the defect, with slightly enlarged defect size after an implantation for 6 months (as + evident from the micro-CT analysis). Bone resorption around the implant was found both in the + groups with cell-laden and with cell-free chondral compartment, which infer the effect from the + osteal anchor rather than from the variable within chondral compartment. The circumstances + described above likely resulted in failure to place the implant in a real press-fit fashion and + hence, in the creation of (micro)movement, leading to increasing instability under repetitive + loading together with possible material degradation over time and ensuing osteolysis, as seen + earlier !unsupported node 'citation'!!unsupported node 'citation'!. + Additionally, the gap between the implant and surrounding native tissue due to the imperfect fit + may have allowed for the intrusion of synovial fluid. Contact of synovial fluid with subchondral + bone has been shown to induce osteolysis !unsupported node 'citation'!. In the + few scaffolds that remained in place, the volume of mineralized bone formation was also lower + than what was found in the earlier study, both in cell-laden and cell-free treatments. This is + potentially due to the higher and cyclical loads experienced in the articulating joint compared + to the tuber coxae. Overall, it was not possible to determine a single cause for the failure of + the CDHA scaffolds to act as the anchor of the engineered osteochondral implant, and it is + likely that the limited osteointegration is due to a combination of misalignment after surgery, + mechanical failure under cyclic loading, and synovial fluid infiltration. +

+

+ In earlier studies + !unsupported node 'citation'!!unsupported node 'citation'! similar observations + were made. In those studies, fibrous repair tissue was seen in the chondral compartment, + together with osteolysis and formation of a fibrous interface surrounding the osteal anchor when + tissue-engineered osteochondral grafts were implanted in a load-bearing area for a 12-month + long-term study. It was hypothesized that osteolysis and the fibrous layer surrounding the + osteal anchor led to instability that might have caused the degradation of the newly formed + cartilage-like repair tissue observed at the early of the experiment. Stability of the + osteochondral graft might be affected by multiple parameters including the alignment of an + osteal compartment within the defect and the properties of the materials being used + !unsupported node 'citation'!!unsupported node 'citation'!!unsupported node + 'citation'!!unsupported node 'citation'!, as these might affect stability of the overlying chondral compartment. In the current study, + misalignment and partial collapse of the osteal part of the construct might also be at the basis + of the protrusion of the chondral compartment of some cell-laden constructs and the inconsistent + position of the chondral graft with respect to the surrounding native tissue in both groups. + These conditions may have led to an abnormal load distribution, possibly inducing inferior + biomechanical properties !unsupported node 'citation'!. It is thus clear that + the imperfect implantation had severe repercussions and can be considered a major factor that + affected the chondral compartment and hence the outcome of the study. This effect was noticeable + to the extent that drawing any conclusions about the effect of BMP-9 seeded ACPCs, which was the + principal variable that was to be tested in the study, is not possible. Also, no conclusion + could be reached about the interface between the osteal and chondral compartments that was used + since delocalized MEW-mesh structures were observed in some scaffolds from both groups. This + might be due to misalignments of the osteal compartment as discussed above, to shear forces + during loading, or a combination of both. +

+

+ During the in vivo post-operative monitoring of the animals, the clinical signs were + very mild and far from alarming, except for the single pony that developed severe lameness. + Clinical examinations were performed routinely by experienced veterinary specialists, however, + assessment of locomotion through visual observation alone is subjective and known to have poor + repeatability, especially in mild cases. This is partly due to the inability of human visual + perception to properly distinguish, notice, and quantify differences in locomotion at high + resolution !unsupported node 'citation'!. Therefore, quantitative gait analysis + was employed as an objective and non-invasive assessment. The gait analysis data did not show + many differences with respect to baseline. This may to a certain extent have been related to + methodological factors. During the assessment, ponies were put on a treadmill and they were + imposed the same belt velocity during both measurements. Therefore, the subjects were forced to + trot at the same velocity, ensuring that stride length needed to be maintained. This might be + the reason why there were no differences between timepoints for maximal protraction and + retraction (the limb parameters). However, pelvis pitch range of motion (ROM) decreased for all + subjects with almost 20% over time. This pattern is often seen in case of dysfunction of the + back. The finding may thus be related to earlier observations that bilateral hindlimb lameness + may induce back problems in horses + !unsupported node 'citation'!!unsupported node 'citation'!!unsupported node + 'citation'!. Toe dragging of the lame limb, in which the hoof is lifted less high off the ground, is + another sign of pain !unsupported node 'citation'!. Nevertheless, the overall + impact of the bilateral lesions in the stifle joints was low, as evidenced by the fact that + there was no sign of load redistribution from the hind to the front limbs. If that had been the + case, the subjects would have compensated by displacing their center of mass more to the front, + resulting in more negative angles for forelimb fetlock extension, as fetlock hyper extension + correlates with peak ground reaction force (GRFPeak) + !unsupported node 'citation'!, where less negative angles indicate a lowered + GRFpeak. In fact, only the fetlock angles of the forelimb ipsilateral to cell-laden construct + changed, becoming less negative, hence indicating unloading rather than additional loading + (lower GRFpeak). The reason for this is not clear. +

+

+ It can be concluded that even seemingly minor modifications of a successful implant may have + grave consequences and extrapolation is dangerous in the complex in vivo situation. In + this case, the failure of the osteal compartment of the construct, the use of which seemed + well-backed by solid in vivo data, did not permit drawing conclusions about the + original hypotheses. Given the relatively frequently occurring, rather disappointing results of + in vivo orthotopic testing of promising techniques for joint repair, it may be wise to + put more emphasis on performing pilot experiments before embarking on a full-scale + in vivo study in a large animal experiment + !unsupported node 'citation'!. Functional joint repair remains a huge challenge + that has not been addressed to some satisfying extent during the last decades, despite many + promising approaches. It is likely that the quest for a real solution will go on for some time + by trial and error with more errors to come. Those errors are inevitable and need to be made but + they should take the least possible toll on experimental animals. +

+

Conclusion

+

+ This study presented the results from the evaluation of a cell-laden and cell-free versions of + an osteochondral implant for cartilage repair in a challenging in vivo large animal + model. The osteal anchor of this osteochondral implant, composed of a bioceramic material that + had previously been proven to facilitate mineralized new bone formation in the same species, + failed to perform as an effective fixation with sufficient stabilization for both cell-laden and + cell-free osteochondral implants. This insufficient fixation was evidenced by the extensive + osteolysis, the collapse and misalignment of the osteal anchor, and the limited volume of newly + formed bone. The failure of the bone anchor hindered the evaluation of the two versions of the + chondral compartment for cartilage repair. The study shows that, even after an equivalent + ceramic bone component had shown very satisfactory results in the same species, minor + differences in the implant and a change in testing condition proved to be enough to lead to + completely different results, in this case precluding drawing conclusions about the effect of + the principal variable. This outcome stresses the need of carrying out in vivo pilot + studies under exactly the same conditions before moving into a larger in vivo study. +

+

References

+

+ Abinzano, F., de Ruijter, M., Mensinga, A., Castilho, M., Khan, I., Levato, R., & Malda, J. + (2018, September). + 9-13). Combining melt electrowriting of microfiber meshes with aggregated chondroprogenitor + cells stimulated with BMP-9 to enhance cartilage tissue engineering [Conference presentation + abstract]. Annual Meeting. + https://pure.ulster.ac.uk/ws/portalfiles/portal/71294610/ESB_2018_Abstract_Proceedings_4.pdf +

+

+ Albrektsson, T., Becker, W., Coli, P., Jemt, T., Molne, J., & Sennerby, L. (2019). Bone loss + around oral and orthopedic implants: An immunologically based condition. + Clinical Implant Dentistry and Related Research, 21(4), 786–795. + https://doi.org/10/gg53t3 +

+

+ Alvarez, C. B. G., Bobbert, M. F., Lamers, L., Johnston, C., Back, W., & van Weeren, P. R. + (2008). The effect of induced hindlimb lameness on thoracolumbar kinematics during treadmill + locomotion. Equine Veterinary Journal, 40(2), 147–152. + https://doi.org/10/bsjkv7 +

+

+ Alvarez, C. B. G., Wennerstrand, J., Bobbert, M. F., Lamers, L., Johnston, C., Back, W., & + Weeren, P. R. (2007). The effect of induced forelimb lameness on thoracolumbar kinematics during + treadmill locomotion. Equine Veterinary Journal, 39(3), 197–201. + https://doi.org/10/djxztr +

+

+ Bal, B. S., Rahaman, M. N., Jayabalan, P., Kuroki, K., Cockrell, M. K., Yao, J. Q., & Cook, + J. L. (2010). In vivo outcomes of tissue-engineered osteochondral grafts. + Journal of Biomedical Materials Research Part B: Applied Biomaterials, 93(1), + 164–174. https://doi.org/10/cn94hf +

+

+ Bothe, F., Deubel, A. K., Hesse, E., Lotz, B., Groll, J., Werner, C., Richter, W., & + Hagmann, S. (2019). Treatment of focal cartilage defects in minipigs with zonal + chondrocyte/mesenchymal progenitor cell constructs. + International Journal of Molecular Sciences. + https://doi.org/10/gh63z7 +

+

+ Boushell, M. K., Hung, C. T., Hunziker, E. B., Strauss, E. J., & Lu, H. H. (2017). Current + strategies for integrative cartilage repair. Connect Tissue Research, 58(5), + 393–406. https://doi.org/10/gf5bw7 +

+

+ Bowland, P., Ingham, E., Jennings, L., & Fisher, J. (2015). Review of the biomechanics and + biotribology of osteochondral grafts used for surgical interventions in the knee. + Proceedings of the Institution of Mechanical Engineers, Part H: Journal of Engineering in + Medicine, 229(12), 879–888. https://doi.org/10/gh63z6 +

+

+ Buchner, H. H. F., Savelberg, H. H. C. M., Schamhardt, H. C., & Barneveld, A. (1995). + Bilateral lameness in horses a kinematic study. Veterinary Quarterly, 17(3), + 103–105. https://doi.org/10/chdqdt +

+

+ Burk, D. L. (2007). Intellectual property and cyberinfrastructure. First Monday. + https://doi.org/1595276491 +

+

+ Crevier-Denoix, N., Robin, D., Pourcelot, P., Falala, S., Holden, L., Estoup, P., Desquilbet, + L., Denoix, J. M., & Chateau, H. (2010). Ground reaction force and kinematic analysis of + limb loading on two different beach sand tracks in harness trotters. + Equine Veterinary Journal, 42(38), 544–551. + https://doi.org/10/dn9j57 +

+

+ de Ruijter, M., Ribeiro, A., Dokter, I., Castilho, M., & Malda, J. (2019). Simultaneous + micropatterning of fibrous meshes and bioinks for the fabrication of living tissue constructs. + Advanced Healthcare Materials, 8(7), 1800418. + https://doi.org/10/gh63z5 +

+

+ Diekman, B. O., & Guilak, F. (2013). Stem cell-based therapies for osteoarthritis: + Challenges and opportunities. Current Opinion in Rheumatology, 25(1), 119–126. + https://doi.org/10/f5k74n +

+

+ Diloksumpan, P., de Ruijter, M., Castilho, M., Gbureck, U., Vermonden, T., van Weeren, P. R., + Malda, J., & Levato, R. (2020). Combining multi-scale 3D printing technologies to engineer + reinforced hydrogel-ceramic interfaces. Biofabrication, 12(2), Article, 25014. + https://doi.org/10/gh63z4 +

+

+ Diloksumpan, P., Vindas Bolanos, R., Cokelaere, S., Pouran, B., de Grauw, J., van Rijen, M., van + Weeren, R., Levato, R., & Malda, J. (2020). Orthotopic bone regeneration within 3D printed + bioceramic scaffolds with region-dependent porosity gradients in an equine model. + Advanced Healthcare Mater, 9(10), 1901807. + https://doi.org/10/gh63z3 +

+

+ Doube, M., Klosowski, M. M., Arganda-Carreras, I., Cordelieres, F. P., Dougherty, R. P., + Jackson, J. S., Schmid, B., Hutchinson, J. R., & Shefelbine, S. J. (2010). BoneJ: Free and + extensible bone image analysis in ImageJ. Bone, 47(6), 1076–1079. + https://doi.org/10/ctk8kn +

+

+ Frisbie, D. D., McCarthy, H. E., Archer, C. W., Barrett, M. F., & McIlwraith, C. W. (2015). + Evaluation of articular cartilage progenitor cells for the repair of articular defects in an + equine model. Journal of Bone and Joint Surgery, 97(6), 484–493. + https://doi.org/10/gh63z2 +

+

+ Goodman, S. B., Pajarinen, J., Yao, Z., & Lin, T. (2019). Inflammation and bone repair: From + particle disease to tissue regeneration. Frontiers in Bioengineering and Biotechnology, + 7, 230. https://doi.org/10/gh63zz +

+

+ Gotterbarm, T., Breusch, S. J., Schneider, U., & Jung, M. (2008). The minipig model for + experimental chondral and osteochondral defect repair in tissue engineering: Retrospective + analysis of 180 defects. Laboratory Animals, 42(1), 71–82. + https://doi.org/10/dxwd9p +

+

+ Greve, L., Dyson, S., & Pfau, T. (2017). Alterations in thoracolumbosacral movement when + pain causing lameness has been improved by diagnostic analgesia. + The Veterinary Journal, 224, 55–63. + https://doi.org/10/gbrrg4 +

+

+ Heuijerjans, A., Wilson, W., Ito, K., & van Donkelaar, C. C. (2018). Osteochondral + resurfacing implantation angle is more important than implant material stiffness. + Journal of Orthopaedic Research, 36(11), 2911–2922. + https://doi.org/10/gh63zx +

+

+ Huang, B. J., Hu, J. C., & Athanasiou, K. A. (2016). Cell-based tissue engineering + strategies used in the clinical repair of articular cartilage. Biomaterials, + 98, 1–22. https://doi.org/10/f8td6q +

+

+ Johnstone, B., Stoddart, M. J., & Im, G. I. (2019). Multi-disciplinary approaches for + cell-based cartilage regeneration. Journal of Orthopaedic Research, 38(3), + 463–472. https://doi.org/10/gh63zw +

+

+ Kloppenburg, M., & Berenbaum, F. (2020). Osteoarthritis year in review 2019: Epidemiology + and therapy. Osteoarthritis and Cartilage, 28(3), 242–248. + https://doi.org/10/gh63zv +

+

+ Kold, S. E., Hickman, J., & Melsen, F. (1986). An experimental study of the healing process + of equine chondral and osteochondral defects. Equine Veterinary Journal, + 18(1), 18–24. https://doi.org/10/cchb7z +

+

+ Kwon, H., Brown, W. E., Lee, C. A., Wang, D., Paschos, N., Hu, J. C., & Athanasiou, K. A. + (2019). Surgical and tissue engineering strategies for articular cartilage and meniscus repair. + Nature Reviews Rheumatology, 15(9), 550–570. + https://doi.org/10/gg8s2h +

+

+ Lee, J. K., Responte, D. J., Cissell, D. D., Hu, J. C., Nolta, J. A., & Athanasiou, K. A. + (2014). Clinical translation of stem cells: Insight for cartilage therapies. + Critical Reviews in Biotechnology, 34(1), 89–100. + https://doi.org/10/gh63zt +

+

+ Levato, R., Webb, W. R., Otto, I. A., Mensinga, A., Zhang, Y., van Rijen, M., van Weeren, R., + Khan, I. M., & Malda, J. (2017). The bio in the ink: Cartilage regeneration with + bioprintable hydrogels and articular cartilage-derived progenitor cells. + Acta Biomaterialia, 61, 41–53. + https://doi.org/10/gh3prk +

+

+ Malda, J., Groll, J., & van Weeren, P. R. (2019). Rethinking articular cartilage + regeneration based on a 250-year-old statement. Nature Reviews Rheumatology, + 15(10), 571–572. https://doi.org/10/gh63zs +

+

+ Mancini, I. A. D., Vindas Bolanos, R. A., Brommer, H., Castilho, M., Ribeiro, A., van Loon, J. + P. A. M., Mensinga, A., van Rijen, M. H. P., Malda, J., & van Weeren, R. (2017). Fixation of + hydrogel constructs for cartilage repair in the equine model: A challenging issue. + Tissue Engineering Part C Methods, 23(11), 804–814. + https://doi.org/10/gch2gd +

+

+ Martin, I., Miot, S., Barbero, A., Jakob, M., & Wendt, D. (2007). Osteochondral tissue + engineering. Journal of Biomechanics, 40(4), 750–765. + https://doi.org/10/fb6fqk +

+

+ McCarthy, H. E., Bara, J. J., Brakspear, K., Singhrao, S. K., & Archer, C. W. (2012). The + comparison of equine articular cartilage progenitor cells and bone marrow-derived stromal cells + as potential cell sources for cartilage repair in the horse. The Veterinary Journal, + 192(3), 345–351. https://doi.org/10/ck96wv +

+

+ Morgan, B. J., Bauza-Mayol, G., Gardner, O. F. W., Zhang, Y., Levato, R., Archer, C. W., van + Weeren, R., Malda, J., Conlan, R. S., & Khan, I. M. (2020). Bone morphogenetic protein-9 is + a potent chondrogenic and morphogenic factor for articular cartilage chondroprogenitors. + Stem Cells and Development, 29(14), 882–894. + https://doi.org/10/gh63zr +

+

+ Nosewicz, T. L., Reilingh, M. L., Wolny, M., van Dijk, C. N., Duda, G. N., & Schell, H. + (2014). Influence of basal support and early loading on bone cartilage healing in press-fitted + osteochondral autografts. Knee Surgery, Sports Traumatology, Arthroscopy, + 22(6), 1445–1451. https://doi.org/10/gh63zq +

+

+ Oryan, A., Alidadi, S., Moshiri, A., & Maffulli, N. (2014). Bone regenerative medicine: + Classic options, novel strategies, and future directions. + Journal of Orthopaedic Surgery and Research, 9(1), Article, 18. + https://doi.org/10/gbftx2 +

+

+ Patel, J. M., Saleh, K. S., Burdick, J. A., & Mauck, R. L. (2019). Bioactive factors for + cartilage repair and regeneration: Improving delivery, retention, and activity. + Acta Biomateralia, 93, 222–238. + https://doi.org/10/gg8sw8 +

+

+ Schindelin, J., Arganda-Carreras, I., Frise, E., Kaynig, V., Longair, M., Pietzsch, T., + Preibisch, S., Rueden, C., Saalfeld, S., Schmid, B., Tinevez, J., White, D. J., Hartenstein, V., + Eliceiri, K., Tomancak, P., & Cardona, A. (2012). Fiji: An open-source platform for + biological-image analysis. Nature Methods, 9(7), 676–682. + https://doi.org/10/f34d7c +

+

+ Schlichting, K., Schell, H., Kleemann, R. U., Schill, A., Weiler, A., Duda, G. N., & Epari, + D. R. (2008). Influence of scaffold stiffness on subchondral bone and subsequent cartilage + regeneration in an ovine model of osteochondral defect healing. + American Journal of Sports Medicine, 36(12), 2379–2391. + https://doi.org/10/c7qbh7 +

+

+ Serra Bragança, F. M., Rhodin, M., & van Weeren, P. R. (2018). On the brink of daily + clinical application of objective gait analysis: What evidence do we have so far from studies + using an induced lameness model? Veterinary Journal, 234, 11–23. + https://doi.org/10/gdmqnn +

+

+ van Susante, J. L., Buma, P., Homminga, G. N., van den Berg, W. B., & Veth, R. P. (1998). + Chondrocyte-seeded hydroxyapatite for repair of large articular cartilage defects. A pilot study + in the goat. Biomaterials, 19(24), 2367–2374. + https://doi.org/10/b74xxn +

+

+ Vindas Bolaños, R. A., Castilho, M., de Grauw, J., Cokelaere, S., Plomp, S., Groll, J., van + Weeren, P. R., Gbureck, U., & Malda, J. (2020). Long-term in vivo performance of + low-temperature 3D-printed bioceramics in an equine model. + ACS Biomaterials Science & Engineering, 6(3), 1681–1689. + https://doi.org/10/gh63zp +

+

+ Vindas Bolaños, R. A., Cokelaere, S. M., Estrada McDermott, J. M., Benders, K. E. M., Gbureck, + U., Plomp, S. G. M., Weinans, H., Groll, J., van Weeren, P. R., & Malda, J. (2017). The use + of a cartilage decellularized matrix scaffold for the repair of osteochondral defects: The + importance of long-term studies in a large animal model. Osteoarthritis and Cartilage, + 25(3), 413–420. https://doi.org/10/f92mgk +

+

+ von Rechenberg, B., Akens, M. K., Nadler, D., Bittmann, P., Zlinszky, K., Kutter, A., Poole, A. + R., & Auer, J. A. (2003). Changes in subchondral bone in cartilage resurfacing—An + experimental study in sheep using different types of osteochondral grafts. + Osteoarthritis and Cartilage, 11(4), 265–277. + https://doi.org/10/bd6pvd +

+

+ Williams, R., Khan, I. M., Richardson, K., Nelson, L., McCarthy, H. E., Analbelsi, T., Singhrao, + S. K., Dowthwaite, G. P., Jones, R. E., Baird, D. M., Lewis, H., Roberts, S., Shaw, H. M., + Dudhia, J., Fairclough, J., Briggs, T., & Archer, C. W. (2010). Identification and clonal + characterisation of a progenitor cell sub-population in normal human articular cartilage. + PLoS One, 5(10), Article, 13246. + https://doi.org/10/bxkqkn +

+

Supplementary information

+

Supplementary Table 1.

+

+ Symmetry parameters (differences between baseline (before implantation) and endpoint (6 + months after implantation) of the study for all ponies). +

+

!unsupported node 'iframe'!

+

Note. Values are given in estimated means (CI)

+

Supplementary Table 2.

+

+ Hind limb parameters (differences between cell-free and cell-laden constructs at 6 months after + implantation). +

+

!unsupported node 'iframe'!

+

Note. Values are given in estimated means (CI)

+
+ +
+ Gait analysis: symmetry parameters. Pelvis roll and yaw joint angles (A,B) showed + no significant differences between baseline and 6 months after implantation and neither did + the kinematic hind limb parameters (C, D, E) between baseline and 6 months after induction. + Limb height of the hind limbs (F) decreased for both hindlimbs, but only significantly for + the cell free group, though there was no difference in limb height between cell-laden and + cell-free groups at 6 months after implementation. +
+
+
+ +
+ Representative radiographic images (latero-medial and craniolateral-caudomedial oblique + projections) of the stifle of ponies before implantation, 3 months after implantation + and 6 months after implantation. + Red arrows indicate the implantation sites. No radiographic abnormalities were noted in any + of the ponies (1st row), except for the pony that became severely lame at 10 + weeks. In this animal extensive osteolysis was observed (2nd row). +
+
+

!unsupported node 'iframe'!

+
+ +
Amount of GAG that was normalized with amount of DNA.
+
+

+
+ +
+ Representative Hematoxylin-Eosin (H&E) staining of 6-month harvested samples + showing a degenerated and necrotic superficial layer at the surface of the chondral + compartment featuring inflammatory cells. + Black arrow indicates area of degenerated cells, P = plasma cells +
+
+
+ +
+ Representative immunohistochemistry of collagen type I staining of 6-month harvested + samples from cell-laden and cell-free osteochondral structures +
+
+
+ +
+ Representative Hematoxylin-Eosin (H&E) staining of 6-month harvested samples from + cell-laden osteochondral structures + CR = Remaining ceramic, NB = Newly formed bone, MEW = pattern of PCL-microfibers, * = + Multifocal foci of inflammatory reaction, # = artifact from cutting, EO = Eosinophil, L = + Lymphocyte +
+
+
+ +
+ Representative Hematoxylin-Eosin (H&E) staining of 6-month harvested samples from + cell-free osteochondral structures. + CR = Remaining ceramic, NB = Newly formed bone, MEW = pattern of PCL-microfibers, * = + Multifocal foci of inflammatory reaction, # = artifact from cutting, EO = Eosinophil, L = + Lymphocyte. +
+
+` as string diff --git a/core/prisma/seeds/starter.ts b/core/prisma/seeds/starter.ts index b0e0785250..5fdaf45d6c 100644 --- a/core/prisma/seeds/starter.ts +++ b/core/prisma/seeds/starter.ts @@ -29,7 +29,7 @@ export async function seedStarter(communityId?: CommunitiesId) { id: communityId, name: "Starter", slug: "starter", - avatar: `${env.PUBPUB_URL}/demo/croc.png`, + avatar: `${env.PUBSTAR_URL}/demo/croc.png`, }, pubFields: { Title: { schemaName: CoreSchemaType.String }, @@ -84,9 +84,9 @@ export async function seedStarter(communityId?: CommunitiesId) { new: { id: memberId, firstName: "Croc", - email: "new@pubpub.org", + email: "new@pubstar.org", lastName: "Croc", - password: "pubpub-new", + password: "pubstar-new", role: MemberRole.admin, }, ...usersNew, @@ -97,7 +97,7 @@ export async function seedStarter(communityId?: CommunitiesId) { values: { Title: "Ancient Giants: Unpacking the Evolutionary History of Crocodiles from Prehistoric to Present", Content: defaultMarkdownParser.parse(faker.lorem.paragraph(1)).toJSON(), - Email: "new@pubpub.org", + Email: "new@pubstar.org", URL: "https://pubpub.org", MemberID: memberId, "ok?": true, @@ -126,7 +126,7 @@ export async function seedStarter(communityId?: CommunitiesId) { values: { Title: "Snap! The Crocodilian Chronicles: A Scaly Tale of Survival and Swamp Supremacy", Content: defaultMarkdownParser.parse(faker.lorem.paragraph(1)).toJSON(), - Email: "new@pubpub.org", + Email: "new@pubstar.org", URL: "https://pubpub.org", MemberID: memberId, "ok?": true, diff --git a/core/prisma/seeds/users.ts b/core/prisma/seeds/users.ts index b1da29cc11..233d2428fe 100644 --- a/core/prisma/seeds/users.ts +++ b/core/prisma/seeds/users.ts @@ -20,8 +20,8 @@ const contributorBase = { export const usersNew = { admin: { ...adminBase, - email: "all@pubpub.org", - password: "pubpub-all", + email: "all@pubstar.org", + password: "pubstar-all", slug: "all", firstName: "Jill", lastName: "Admin", @@ -32,8 +32,8 @@ export const usersNew = { }, editor: { ...editorBase, - email: "some@pubpub.org", - password: "pubpub-some", + email: "some@pubstar.org", + password: "pubstar-some", slug: "some", firstName: "Jack", lastName: "Editor", @@ -43,8 +43,8 @@ export const usersNew = { }, contributor: { ...contributorBase, - email: "none@pubpub.org", - password: "pubpub-none", + email: "none@pubstar.org", + password: "pubstar-none", slug: "none", firstName: "Jenna", lastName: "Contributor", diff --git a/dev.Caddyfile b/dev.Caddyfile index f8ece58b87..21f1093e76 100644 --- a/dev.Caddyfile +++ b/dev.Caddyfile @@ -45,13 +45,13 @@ # visit: http://localhost:8080/my-community/journal-2024/ handle_path /sites/* { - import s3site {$ASSETS_BUCKET_NAME:assets} {$S3_ENDPOINT:garage:3900} + import s3site {$S3_BUCKET_NAME:assets} {$S3_ENDPOINT:garage:3900} } # simpler path without /sites prefix # handles /{communitySlug}/{subpath}/... handle /* { - import s3site "{$ASSETS_BUCKET_NAME:assets}/sites" {$S3_ENDPOINT:garage:3900} + import s3site "{$S3_BUCKET_NAME:assets}/sites" {$S3_ENDPOINT:garage:3900} } } @@ -72,7 +72,7 @@ # # { # filesystem sites s3 { -# bucket {$ASSETS_BUCKET_NAME:assets} +# bucket {$S3_BUCKET_NAME:assets} # region {$S3_REGION:garage} # endpoint {$S3_ENDPOINT:http://garage:3900} # use_path_style diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index eb6607257c..b254a81bcd 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -59,7 +59,7 @@ services: - path: .env required: true healthcheck: - test: ["CMD", "echo", "true"] + test: [ "CMD", "echo", "true" ] interval: 1m30s timeout: 30s retries: 5 @@ -86,8 +86,8 @@ services: volumes: - ../dev.Caddyfile:/etc/caddy/Caddyfile environment: - - ASSETS_BUCKET_NAME=${ASSETS_BUCKET_NAME:-assets.v7.pubpub.org} - - S3_REGION=${ASSETS_REGION:-garage} + - S3_BUCKET_NAME=${S3_BUCKET_NAME:-assets.pubstar.org} + - S3_REGION=${S3_REGION:-garage} - S3_ENDPOINT=garage:3900 ports: - "8080:8080" diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 5a61122955..55f8753514 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -2,13 +2,7 @@ services: minio: image: minio/minio:RELEASE.2025-04-22T22-12-26Z healthcheck: - test: - [ - "CMD", - "curl", - "-f", - "http://localhost:9000/minio/health/ready", - ] + test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/ready" ] interval: 1m30s timeout: 30s retries: 5 @@ -24,21 +18,30 @@ services: minio-init: image: minio/mc:latest # basically what this does: - # - create the bucket + # - create the public assets bucket # - allow public downloads for said bucket - # - add a new user - # - allow said user to upload + # - add the assets uploader user + # - optionally create a separate private backup bucket + backup user # this sadly can't be done by just configuring some env vars for minio itself, very annoying, you need to use their mc client. I'm not sure this is the best way to go about doing this. entrypoint: > - /bin/sh -c ' - /usr/bin/mc alias set myminio http://minio:9000 "$${MINIO_ROOT_USER}" "$${MINIO_ROOT_PASSWORD}"; - /usr/bin/mc mb --ignore-existing myminio/"$${ASSETS_BUCKET_NAME}"; - /usr/bin/mc anonymous set download myminio/"$${ASSETS_BUCKET_NAME}"; - /usr/bin/mc admin user add myminio "$${ASSETS_UPLOAD_KEY}" "$${ASSETS_UPLOAD_SECRET_KEY}"; - /usr/bin/mc admin policy attach myminio readwrite --user "$${ASSETS_UPLOAD_KEY}";' + /bin/sh -c ' /usr/bin/mc alias set myminio http://minio:9000 + "$${MINIO_ROOT_USER}" "$${MINIO_ROOT_PASSWORD}"; /usr/bin/mc mb + --ignore-existing myminio/"$${S3_BUCKET_NAME}"; /usr/bin/mc + anonymous set download myminio/"$${S3_BUCKET_NAME}"; /usr/bin/mc + admin user add myminio "$${S3_ACCESS_KEY}" "$${S3_SECRET_KEY}"; + /usr/bin/mc admin policy attach myminio readwrite --user + "$${S3_ACCESS_KEY}"; if [ -n "$${S3_BACKUP_BUCKET}" ] && [ -n + "$${S3_BACKUP_ACCESS_KEY}" ] && [ -n "$${S3_BACKUP_SECRET_KEY}" ]; + then echo "Creating backup bucket and user"; + /usr/bin/mc mb --ignore-existing + myminio/"$${S3_BACKUP_BUCKET}"; /usr/bin/mc anonymous set none + myminio/"$${S3_BACKUP_BUCKET}"; /usr/bin/mc admin user add myminio + "$${S3_BACKUP_ACCESS_KEY}" "$${S3_BACKUP_SECRET_KEY}"; /usr/bin/mc + admin policy attach myminio readwrite --user + "$${S3_BACKUP_ACCESS_KEY}"; echo "Backup bucket and user created"; else echo "No backup bucket or user configured"; fi;' db: - image: postgres:15 + image: postgres:17 restart: always inbucket: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4ad3c773a8..4fdd1543e6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -69,9 +69,7 @@ services: # caddy with s3fs for serving static sites from minio caddy-sites: - build: - context: . - dockerfile: Dockerfile.caddy + image: caddy:latest env_file: - path: .env.docker-compose.dev required: true diff --git a/docker-compose.preview.pr.yml b/docker-compose.preview.pr.yml deleted file mode 100644 index c1e1c23609..0000000000 --- a/docker-compose.preview.pr.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - platform: - environment: - PUBPUB_URL: ${PULLPREVIEW_URL} - caddy: - environment: - PUBLIC_URL: ${PULLPREVIEW_PUBLIC_DNS} diff --git a/docker-compose.preview.sandbox.yml b/docker-compose.preview.sandbox.yml deleted file mode 100644 index 9c9123799c..0000000000 --- a/docker-compose.preview.sandbox.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - platform: - environment: - PUBPUB_URL: https://sandbox.pubpub.org - caddy: - environment: - PUBLIC_URL: sandbox.pubpub.org diff --git a/docker-compose.preview.yml b/docker-compose.preview.yml deleted file mode 100644 index 0058e23566..0000000000 --- a/docker-compose.preview.yml +++ /dev/null @@ -1,61 +0,0 @@ -services: - platform: - image: PLATFORM_IMAGE - environment: - POSTGRES_USER: preview - POSTGRES_PASSWORD: preview - POSTGRES_DB: preview - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - ASSETS_UPLOAD_KEY: preview-different - ASSETS_UPLOAD_SECRET_KEY: preview-different123 - ASSETS_STORAGE_ENDPOINT: https://${PULLPREVIEW_PUBLIC_DNS}/a - FLAGS: uploads:off,invites:off,disabled-actions:http+email - ENV_NAME: sandbox - DATACITE_API_URL: https://api.test.datacite.org - DATACITE_REPOSITORY_ID: DATACITE_REPOSITORY_ID - DATACITE_PASSWORD: DATACITE_PASSWORD - - minio-init: - restart: on-failure - environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - ASSETS_UPLOAD_KEY: preview-different - ASSETS_UPLOAD_SECRET_KEY: preview-different123 - - minio: - environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - MINIO_BROWSER_REDIRECT_URL: https://${PULLPREVIEW_PUBLIC_DNS}/assets-ui - # volumes: - # - ./minio:/data - platform-jobs: - image: JOBS_IMAGE - platform-migrations: - image: MIGRATIONS_IMAGE - restart: on-failure - command: pnpm --filter core reset - caddy: - image: CADDY_SITES_IMAGE - restart: on-failure - depends_on: - - platform - - platform-jobs - - minio - env_file: .env - environment: - - S3_ENDPOINT=http://minio:9000 - - S3_REGION=us-east-1 - - ASSETS_BUCKET_NAME=assets - - ASSETS_UPLOAD_KEY=preview-different - - ASSETS_UPLOAD_SECRET_KEY=preview-different123 - ports: - - "443:443" - volumes: - - ./caddy:/etc/caddy - - caddy-data:/data - - caddy-config:/config - networks: - - app-network diff --git a/docker-compose.test.yml b/docker-compose.test.yml index bbb0f2d557..0dce915870 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -77,7 +77,7 @@ services: env_file: .env.docker-compose.dev environment: - OTEL_SERVICE_NAME=jobs.jobs - - PUBPUB_URL=${JOBS_INTEGRATION_PUBPUB_URL:-http://integration-tests:3000} + - PUBSTAR_URL=${JOBS_INTEGRATION_PUBSTAR_URL:-http://integration-tests:3000} networks: - app-network depends_on: @@ -101,7 +101,7 @@ services: condition: service_started # platform: linux/amd64 healthcheck: - test: ["CMD-SHELL", "curl http://integration-tests:3000/api/health"] + test: [ "CMD-SHELL", "curl http://integration-tests:3000/api/health" ] interval: 10s timeout: 5s retries: 5 @@ -115,6 +115,28 @@ services: extra_hosts: - "host.docker.internal:host-gateway" + site-builder: + image: ${SITE_BUILDER_IMAGE} + depends_on: + - integration-tests + - jobs + - minio + ports: + - "4000:4000" + restart: on-failure + networks: + - app-network + profiles: + - integration + environment: + - PUBSTAR_URL=http://integration-tests:3000 + - S3_ENDPOINT=${S3_ENDPOINT:-http://minio:9000} + - S3_REGION=${S3_REGION:-us-east-1} + - S3_ACCESS_KEY=${S3_ACCESS_KEY:-preview-different} + - S3_SECRET_KEY=${S3_SECRET_KEY:-preview-different123} + - S3_BUCKET_NAME=${S3_BUCKET_NAME:-byron} + - PORT=4000 + volumes: minio_data: postgres_test_data: diff --git a/docs/content/development/testing/1-e2e-tests.mdx b/docs/content/development/testing/1-e2e-tests.mdx index a4795a16eb..114742addd 100644 --- a/docs/content/development/testing/1-e2e-tests.mdx +++ b/docs/content/development/testing/1-e2e-tests.mdx @@ -10,12 +10,12 @@ We use [Playwright](https://playwright.dev/) for E2E tests. 1. Install Playwright Test for VSCode plugin (should be recommended) 2. `pnpm -w dev:setup` - - (TIL: `-w` lets you run things from the root without actually being in the root!) + - (TIL: `-w` lets you run things from the root without actually being in the root!) 3. Click on extension (test tube icon), click on refresh button (if the tests don't show up), `core` tests should appear - - you might see an error to install `@playwright/test` but you can ignore this! - - (20250605) The `context-editor` tests currently do not show up here. + - you might see an error to install `@playwright/test` but you can ignore this! + - (20250605) The `context-editor` tests currently do not show up here. 4. Start your dev server via `pnpm dev at root` - - This makes sure `core` and `jobs` are both up, which is needed for some for all playwright tests to pass) + - This makes sure `core` and `jobs` are both up, which is needed for some for all playwright tests to pass) 5. Now you should be able to either run a test from the testing panel, or directly inside a test @@ -56,9 +56,9 @@ You can debug either using `VSCode`s built-in breakpoints, or by manually adding Set a breakpoint in the test by clicking Break points In `VSCode`, you can either right click a test and select "Debug test", or click the debug icon next to the test in the sidebar. @@ -70,12 +70,12 @@ You can also use `playwright:test 'path/to/test' -- --debug` (note the extra `-- Then you're able to use `await page.pause()` to pause the test at a specific point. ```ts filename="some-test.spec.ts" -test("some test", async ({ page }) => { - await page.goto("https://example.com"); - await page.pause(); // look at what you've done +test('some test', async ({ page }) => { + await page.goto('https://example.com'); + await page.pause(); // look at what you've done }); ``` #### Misc -Currently (2025-06-05), data seeded into playwright doesn't add all@pubpub.org which is why if you are logged in you won't see the seeded playwright data. it's sometimes useful to be able to see the playwright community in your dev server—in this case you'd have to log in as the user in the seed community +Currently (2025-06-05), data seeded into playwright doesn't add all@pubstar.org which is why if you are logged in you won't see the seeded playwright data. it's sometimes useful to be able to see the playwright community in your dev server—in this case you'd have to log in as the user in the seed community diff --git a/docs/content/infrastructure/nginx.mdx b/docs/content/infrastructure/nginx.mdx index e2aee8c85d..3106ce01d3 100644 --- a/docs/content/infrastructure/nginx.mdx +++ b/docs/content/infrastructure/nginx.mdx @@ -8,7 +8,7 @@ This simple container runs nginx, listening on a port (typically 8080), and forw all traffic to another host (typically `127.0.0.1:`). In ECS, all containers in the same task are hosted on the same network interface and therefore have the same IP. -The specific reason this container is needed in Pubpub-v7 is that: +The specific reason this container is needed in PubStar-v7 is that: 1. we have one DNS name that serves the whole application, which is backed by multiple ECS containers. 2. these containers are routed to based on path prefixes. diff --git a/infra/.env.enc b/infra/.env.enc new file mode 100644 index 0000000000..2dcedf9c82 --- /dev/null +++ b/infra/.env.enc @@ -0,0 +1,64 @@ +#ENC[AES256_GCM,data:/x9mh2h3/W7lQmxpEh02jhxheIKLiA5aGAJ1Bill/7Yr,iv:RXPgkOsUnyxRgrOB6O/2ujGqe/cy4vORHBc52pT0Ml0=,tag:Lvtr82v9uTgATddyWhh5DA==,type:comment] +#ENC[AES256_GCM,data:uPl+lleVHirSTof4XJOYKtkRYvZnwlHyUrV1xox7RX0EKmVkBHVh7El03DD/HqgDR4v+ZxNyCb9IAII=,iv:wznD+/ufycu9rMpQmW/+9V6WFaS1hax3cPuHICQXeh4=,tag:Sy8smZ+0iH/P+AfrKB3cIg==,type:comment] +#ENC[AES256_GCM,data:eMYdfBica9VOMVMnDbcFyrCFtbTUjcxC0ISNnWkF+mbrS6ry/Tv1iqCyEQFoKUr9NdzeyKuBqJbkOdttjXYrIQUr6Q==,iv:nAhGWgbd5ZOaoA/1KwarFG9ZMURA1BHeaIRAmtHBbrk=,tag:2Np2ZK+Rvr3gtkkPlwsCeQ==,type:comment] +#ENC[AES256_GCM,data:xR0W8UoU6FQA67V3XKNeP3kAqjioIrMR3YmrdCCYu0Bcpfk3rwrneE2uK8VzYAVvNmw=,iv:6I/4YJ+voUkBwoeUC/oXM+IjXv7OrJDoB+Q9sP4tQYU=,tag:hUz+dwQnNLBou9E/CGG1Xw==,type:comment] +PUBSTAR_HOSTNAME=ENC[AES256_GCM,data:uXR0UForv46Ldpbd0UIE,iv:HzJQXvhY8lABFLHcWPQXLZyX6ihbjEkPwd00w76YrcM=,tag:hQC8fN3ck35nMoNgSh5LEQ==,type:str] +PUBSTAR_URL=ENC[AES256_GCM,data:sjkhrE8/0LlLuHkDbA+DfeuoS/Y711o=,iv:w1EMLoG/3H5bZx+H0k7q1SeGtfQ/hge6PAVqwQv4hdw=,tag:XnyHd7NM3E3qMMnA/Soq+w==,type:str] +POSTGRES_USER=ENC[AES256_GCM,data:ceSoClohrqU=,iv:zt6lTBXJwK8ZjC9qBGwjC2KrC3e8Y7350ilzscOkRFc=,tag:HFeUxlII/uZW7TQg9gAjpQ==,type:str] +POSTGRES_PASSWORD=ENC[AES256_GCM,data:TNRk9Cbj0BNV/7sSoEMFZMi2+7WNciCesRnkMeAP9AGfm5qOO9Igsb4TNMh20nFCqYhnDyoC7y18ao2XzZtCbA==,iv:5HYCVOrS0c8iH8NVX2EPe+8f6hrusoxdzfy1XOc9MeA=,tag:dP1dTV36DJLS+eZUpLWoHw==,type:str] +POSTGRES_DB=ENC[AES256_GCM,data:cmmimJJ+DQ==,iv:bNS69OYxWVv+mzJWONUKu5Grx25W3nqiRo3BSrOBnak=,tag:FH/A36WyAZmLkDE3mDKGGA==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:iVZxRKryPjzS6HxHnh47A9dYptnlD6hfu/utW4WvZRxKmaJ5rGt/WCDcQTmaJuZygkQnYNTsUExBbMVwamGfCQLzdA9dfX6ejd9ctRs+917rS9y3gi7huVyx+4qWmdcwlroufLFy,iv:4/wFLb3gtS5PaTCXqz/l1uehCFWKJqDvho2ac4fxBa4=,tag:slN5qRDBlkel9KDcHN4y5Q==,type:str] +PGHOST=ENC[AES256_GCM,data:ilA=,iv:iE1GRKmxbToTlPlizcZUDPFE7+tznI/ns3TxDe38bsw=,tag:F8Iq9AbWOLtUdnVtNQgVMg==,type:str] +PGPORT=ENC[AES256_GCM,data:1s8LNw==,iv:+9xn9QLZ+C9l/lEm6cfWtej1UHumXB+DsSCT0LopbqY=,tag:rb3r+f90n75NbId5e9CWqQ==,type:str] +PGUSER=ENC[AES256_GCM,data:u3H2gZqmJEg=,iv:Qqllqbu4tdt77HbPDjMvDeR1df3OwIJYSCIsEIPBFdQ=,tag:1BjzA8P87fmbrYHXrcbbtQ==,type:str] +PGPASSWORD=ENC[AES256_GCM,data:k/w24sL1KDk6N82MMF+ZTukLbGbzGqiX6oTVbtrmwPbf2czNdNob1WPXSHm2tumOrx5qupCBU324+F95AT58fg==,iv:zN/WtYQ8UpxH0CCUX2TYDv+EUti834itU9XIRmZ5Jvg=,tag:5hqQ3G4AHAs7Exb4S7HdAg==,type:str] +PGDATABASE=ENC[AES256_GCM,data:hntknEwCeA==,iv:fqSsHDyVlwV1ondZQz51z/9IBtGR7nDPsfxBPppVeFA=,tag:gDTNqlssqP2rTW6IUbd7Cw==,type:str] +VALKEY_HOST=ENC[AES256_GCM,data:+ZOnknw=,iv:vr83FoDBXnXMvDa7rETUoT5NUbUMdaZFuiyy9AmKP3o=,tag:hYi17eyNVWVea2j0vHivbQ==,type:str] +#ENC[AES256_GCM,data:ZyzlLV31lUzfkCXlECPjlWBsnJA1E2jCxuOsJNgp,iv:CTtAVwYoe5ZNhcdw0gbJP+u6LntDzlHw6WHkIjRgomk=,tag:YLSptWEv5QsxNs6+HC8Kyg==,type:comment] +#ENC[AES256_GCM,data:dJkLZELxxAK6CmAo5RELqn9L9uDmiDOL4EytZrkTYeBbjA==,iv:49Zr365GhKQ2gLA8K2OYGVthKfAHe0aVhaKW08uFMDw=,tag:pPNcbMQ74OQCeGI6XjBKpQ==,type:comment] +S3_BUCKET_NAME=ENC[AES256_GCM,data:/0N4Kwa9PGEV8ETB0f1aM6/8,iv:dWm+MGMLn8F1Rpp+Uk+m0xjPsOtszghOBZNr89p9DQE=,tag:aC7xIVdqmKQTU+vCmYKDjA==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:k5ig2CksW0vewzFX6/OhyW7xEeY=,iv:j4P5JtXx4X5byPKbJo5nnrU14yjyjwhzzwHPZj7xzbQ=,tag:KeB2g0XRQEATbxsW04W2Wg==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:sbA49TRHn5EglwKNnqUJJgQq834t7x7PJgUVoRabkImBhHEfrdr3Jw==,iv:0dCY5l8ximmL2nqyqyTmU/b03AE7jH3t1zv+vw7Hq+A=,tag:5VCdjiI06ZeshJqwrdzw9w==,type:str] +S3_REGION=ENC[AES256_GCM,data:86m8krMCui1h,iv:h0RsKw17tUnN09FwiRIwsbTxU4Ltv0tKqlVqjzKwpUI=,tag:HeraHLJRW6RFjywg3qDPDA==,type:str] +#ENC[AES256_GCM,data:UCoLzGwidkbc/Lf5VWtu6aqvTMJgkjsoy0ICsc06,iv:9zJbJc+5aBAaHsNKe6iacV9cxk9pl8CypJd1t6GN/NE=,tag:N76V1dolqhaJIMZCLt99Iw==,type:comment] +S3_PUBLIC_URL_STYLE=ENC[AES256_GCM,data:E7IR56MCtDWC,iv:/0rpNjx50uCBaO0JJ/JajyXoJw8J8nmnVPkhu183+uo=,tag:A7lAG45n4rrpBNECxylRkw==,type:str] +SITE_BUILDER_ENDPOINT=ENC[AES256_GCM,data:vPjCv4vscvlE2zrpOFEi+7kytev1foeY,iv:gUAImqqFo5r8CZPMJIWQD75ku4ijsgQKam0TYU6AiP0=,tag:OAOZUnmvUpq97T8WurjB+w==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:lnedLxpMlVV4ej0XUIU34j7sHGIeJFkdKO/7cQ85WlfyYw==,iv:2bYYWIKqGRcRqqOzPLBc5Oe4X14LpyUyQW71/rH69fQ=,tag:O5xo8am35fuH5ayKvMpMsw==,type:str] +SMTP_PORT=ENC[AES256_GCM,data:L+6I,iv:WRbrslK6rA6R+15/CQFVzEMj69ZCulP+XVo4fJXdGgE=,tag:1vt+eWAOMupZjFXisA8Byg==,type:str] +SMTP_PASSWORD=ENC[AES256_GCM,data:JwvQxAlxYd+0uMFxd5sxveZoW/O8ErmvrGeptx4ru+ftYS4XO28gcjALDr0=,iv:8GdZRrSLyWx/+aDEwTGuTJ4UWyJ3g3Gv6Dh8HaEVep4=,tag:3TXr2EMy2WMNo0hkW3ZTpA==,type:str] +SMTP_USERNAME=ENC[AES256_GCM,data:66fTH6xmu6MKh3Ca+IJW8Sj7fcw=,iv:UGSxjnaN72c5JHNM7WCyb8b+7UGVtLsQkgmr3RjF9OQ=,tag:3pU5dQfKLJ7tPuAZr0vjjA==,type:str] +SMTP_FROM=ENC[AES256_GCM,data:m8sXZ3aIwjLKyNuU6gCl,iv:WKj3uK4BGuAJ2r+Vx2PJmXRbfWuvvpX64yHiVxd2xAs=,tag:k6lGSvKEBXQjdrWggC+o8g==,type:str] +SMTP_FROM_NAME=ENC[AES256_GCM,data:JF/d3sAsC0havTnB,iv:3Nytq+08LBRI94RzgGlizqBEBgxw6BJ38UdEc14SOdM=,tag:96d1gAzQQTyVgVi0rnQKSw==,type:str] +GCLOUD_KEY_FILE=ENC[AES256_GCM,data:JCx1hyG9bi8iYU2luHcvcmmiGcOu71PNmz12A2LTTV8rDnhDWbOkioNmTxBFjTfAlTVsThwQIQBJ1k7UXpIicQocLQUgdlUKCpvN6TQ6RSfR97jsrr2mqBKYQqdyyrdmcY+2BNxiZaPXNwjVNTx9FisaP0/khhYrNOgxYnIKPU61dtwMyOhuiCNrGfiJmD/zLmh4WvQKUl5j7VzVv2QRfZ2bpAqVvjDO5gd3E2DMjGhSVjBNBmLut8J7Cq5g+q7WF/74Fg7rQ6XtponV0W/qWxEAT4te664t6iHxJ3bVSkrp6jyo5HSdgQ9eGgRtJqP/YrVu0PItf1lIqyEY322xWlLbKVo8e70gU+VjFAJb/fII3uKXfZyzbFf2e2/yqc7F5IPuOOznq+aGb6vSaRP8rD1+WQyrI3GFIub9HgKMC6zznhTgcekNhlDsVzLhltt+psULZxfbtByMwIzUCZsMDVkKeyARkSDMKMrX49NQWWbO8qbRLdsq5oHWam4+rCtHdv2QUdXOHiv0KZEWK03J9wadM0LZFkWmLAA6gE3UdhCZAvWJ+eKXYL7dysscNKqRJNU8KA6rkhd+IgujBCWtFx4dYuwyteHYml/DxzAf1GMwr2ABJIWNIzPqD2HtS1srXl5aJLnBcWscEmhL50G177meaRfb5QWdf3BoVGfnFpXj3BpDs/QH2UILJiLAt3K+QtKXcyY3ZJ6B3jNzUzysjsa/fyII4vGzTWOsJsqeeM1Ewe//0XMzghtgdW5p866kd9L/T2sPAEW0ZASLhmkL0suna/M4d3retsxluvBHxQ1E5XQ6GI4eTI6qpNwZQiuMrFyllP00SrCvq5Nr85iMlkRIOrGzG5P1VC9ASDaVi3HZR0m+MEeiY9vANMR9sXn6qbJ9+vFG7sXgY5uZ3nmSngYT7Tj76xaYTPtHY104i7XrJERyMI0owfQaVrXyrdL1oNgKzFb0lMyVtWfASbnhaIZlOiRLyJT2XS8npHO4tLV8YioW7ExKukdZu5J/8fJN6bEI7DsqryrdxHPsXnqpQIu2dYiylM/UAoEHGolZ45rqs8JYpUtPB73CrMThkuuwzVpCKGy11lHpBQHZiLrYOtRG5bm+ky+1rGlt4gP2EZAlogluK/Q/k0ZRURUKvA7WtGXrmfevswUqDIBLta7gw+D4vjXTfQmg3SCiE5TzWE9mhy5tW3u5Q6uym5+k6Ru7/qYBZd15yCX4Lgo1FbtO46Mn87tVfqhXDQdSGWhlrpOuyNbq7ANaEaXJxILRvtAsw1L9VNnIJhZLZcD2F3IkbnOqIB2wzhoeBdQNWtyQydvCybC2pt8yJCQonN0piMUct/jReeMmeG9mXQayMO0GgZXhPcpzxovWC6qhdCIkSD1WvoFyPVPSuWpYxtrsoOKpcPvTDwTHhFemngMvX9+8FJv2t6O0jUx3eJJkXbrx00l88e/HluWhZ/QG/GEqn2zxnIvXxGTWR7LNZLtea9Pbl35DFYIQFJCmLzyHpwzQ4MV/abcjFFgAGx9gOrx6od3CGzE27L1RB+rePncPXvGKhCUE059JoGT2EguCRc6OgK/3fuLS2UAexQhSxYj08oNk29fJTpatzdf8M5o7sGmEeKc3CVFc6lzvApPg3/7iJrchrEdXUrq4ibKuCPj/Xed+nzK7fyc6nPBSBI+i/dJ3sXuDXkJ9P0geXtn2MC3f0zw6Hr1sfPvA56WrZuGkCJi3Z8sG39kOwI15w32srQbxAlUl81X+iJdeMjXZP5+leZsDpcmsdnUvODuVaQakV+dYyTlWoSMiIxKZXMhdMsHvDwYSeqo6qaI6Y6M0jWlq5LTAcySBXiXtKSPm5n+HN1Ag8BBkLEeI70d0cZuOXxinWyWjFx1BaAX+JkzVIT3ppYU7AhKNaxJmOSk+2xSU7vb4Y12IKHl60oaJzej/nvQWGcKw2FHacvRhGICFDffOnuHiuKktE/5+Of2yz06KLnItvG5H/TYe+StkBgdWHQI4nPZd3euRy1Q1vjWWOHuFpkDNfn/eJ4nSxhHt+XFTt9hu3tuBGgDfdLHU1WneDTEAx1C5qYDK6og/OYtMM3dRMZa+YWu25tDFN7t6l+jwZUJcvO8AlI+O5P/AETBc1X8Y4k7f01HLUC7hLBR2ERhU/nXuwo7SGs29BjQhVLqJs8IzMhmnkqEF44hcCJ8TqPRQbt+l6Yi5zX/exbngS3AV/v/oQFLjnhkrev8666TTwLhBBqKBKCj1jGts07KER593gYvKYCgleFzAMPI8MzfcSRaXvIdOZw1+4bYHCylmVGMLgbdtqZGzt2d2Ayv6+IrYtI27W47AcAfgUOri/hdAa950ZsWuX6h3P6rA3GRgVFv/FtzEsf66yUsUi6jTiClOei4aqQ75t5Fa5VdNk03/FwCMwx1VugJMtSHEpMFu6eDGQv4ZoHIsnLYGA9EeciDRxk0v3V7goAnjkR8nyZKDL/f3OUSoz93LUqAmTEJajKsG7vMf6eLUSrWg9aEnDs+c89gvX3oRb6rDzIxrgs4XSvNfjGG23GSY53jnEDoDGeHBdH1bd1qE5VXcWYhSYBmySwTJUbDHLFFM5R6om+MpJNgjC/BLZkCiAavdxczODIMPKU2mONknZ9J6gHXRpBRVkHCuAqv6Ixhggz6vfsL6Wfd+83v0fKKTAAB9uegEKmQ5L/ZgPP+2nQoCWUPSzqmK2M9/uuyppvlp+GXcpgbyKQNvM7j8m+E9XmgeXUomZyieBTK0R08WmOWroCCB5JejY0tMYAwdfbBV7C2LRJdaHaEFI+YnLVgHP8bQFqbMPhafnJJWNmOjjp/bWza3pnNSC0BDulLOQYGiGSlqX2Dc/0q6JRR+rNqKTnLGKHudRCAV9OxuGNHJGaViXH6c1LWMW5EyYhXskbhfpt34hJuBoeCyAaLCaW0/Q8zz7vA4tGyHSvfjFpKerw38v3JchIkhcvgaTpn1JqI7N8l3cun8bshsGGkBYC1cFBopa/YJ+WOUpl1cPtzk7j/qgDvP/fXQSuMc/UIlErPHP7vriE6YdbYsvCNzTIUNx4+L+rWVKbAiK5JSqrY5tvfPYF8IAAxmUUzb7Li5dWB7XQzAtXJp+nYhGXbqKgtmdEaR5hi/JV8rzzosjSgy6mzLrm+Q7AtZIcY4OzuO6liiawGtYvym5Ft+L7PORGTMPF4k6AGxmNt4NSMnqBCEc29bMT82dV4+xnbYGnad59Zt7tPRUtwT63xWXN48Wfmsp9+7BaqJ6R+SyFGbUFwzh7Kr9LbOg0fZkJr84hFHmaWGdqT4WV+QzznniZnHWWMHL14ZTrEeGpE3IC+iD9/zo8OwgxLc8059ieczS+XQEGWmc6dBPcUHvslXjQ6quQSb9qey8UcPlWYdUiW3CMwS5IOfxFwZGbgavuoLNn/X984gAUcueTYUYNEpWsfzcNmCUcQxbQhfl1f1jBzdfu9gE3C3NiwxNd2Yq1dymaynMyXmQEkbq1qaEeo7eJy7trHQ+QGKbhhrWbop0GUrklYjAyCiwR5c/JDDjvAP/PTvBrRAAYkDA3OOG5q49ZHQi2EwyckTOEhH1yCQ3AHHYq/FekekdIqniZRukpU+wiQXAtW96oIHU6uwivpdUa4gIXLxpcXwAOFUK0zofpGW3jhozNsXgPd3QOE2tX0/0eFTdj6hZoCZpndLuQ0AGJyjjxdljQwfKvn2jlke2Sks0brwcrjUv9gB6I4Z/mwUKbgOYbLWjkz36t3HtUYpPXvo8s3scq3AUbn7Npg7fwFkirrYe9Myb2G6tr8kIk8c38ETxhFCze5bGb9nclYlPxWkcIksSNi50vHbf30ppp7GqJMjDW/xBCRG042hYSUN4OEENJwZj44lzBgLcpyuu1u3dHfbQTg97+2i88VFxyTB3Zb+o8nERBqYUHMgU7Yo2RACOt1SUgsi0VtjrQuagI+Tdru3yJ3up4e0QJ8ouG5CFjSOrKNzZDDq2FhMvpWjIuzT8pzMHVQwRvW3wtQG/IHIcW+YByXOjAgCKu+32982K4rGpTnUFom/qot5XQ+ouQt9dgey1RvA0g7JB54eeOkeVgoR4AyrrGfngpCs2Sz+5MhYLGNa/pojNA4LVaNpQmzlrHVpLT4ia/xRK9si5XHA6GBdmVtPQUS9JjBboe92JQ==,iv:pn6NwOiNvkwFuiYbJ0Y4NrMWh2lThK2miAVSAKAzuYg=,tag:OXpeHPCZViYb8B+2ecN5qw==,type:str] +OTEL_SERVICE_NAME=ENC[AES256_GCM,data:OqPFBv4KcA==,iv:G+mR7HX5zNkoTXSlOujueDlMF+gke1q8R5PcpCMrNUY=,tag:eZxXMyCNT/CCFXXFUnLbwA==,type:str] +HONEYCOMB_API_KEY=ENC[AES256_GCM,data:fvNKONRMGnTcvAFjSKPkfsQ1ZAzYb3nYOagf6+GEV6PPVb6/4RwqjvO2Ekliq5dohJth2l9V4cfXvfUJVr5xnQ==,iv:bEeRXZgCRvcyKNWsaoLZB9e/k8+wb8n/13GwaIpsKkQ=,tag:IdektxuEzr0n5LDL11l8UA==,type:str] +API_KEY=ENC[AES256_GCM,data:9tl0a7AgM/r38AmdfDUeaEko31S0kV14nt4PxLQqLgs=,iv:k/J47QAZ397DjyKW9gUDjgypmxzGXMB/wU97ywkEvks=,tag:oylYJFlhRzOIZcFqSgakyw==,type:str] +DATACITE_API_URL=ENC[AES256_GCM,data:U3kKulrGCKO6wrjIHqtnlUoO8aoUoSZL,iv:9gmJZg9c1zzVE7f8eXszbDhq5ffXE5f1UArjk/T9RSA=,tag:bLXtguJc6I8Lrg21VK7khA==,type:str] +DATACITE_REPOSITORY_ID=ENC[AES256_GCM,data:t0ul1CQ8wVHu+rY=,iv:75VF0CtrI5GHeqpor6mSWlHgnq+puz4dcqyGLq3tV54=,tag:l7yDBvpsz7qjtIp/+o5Fww==,type:str] +DATACITE_PASSWORD=ENC[AES256_GCM,data:xj/uZAVEWzykbY0qHXqs,iv:Uf0Ilo0RggxXAyTCpyKNap6ItZlQWiwoqQ+PqiznRkQ=,tag:4+OAdk86MYa//RFp2dJnFw==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:pKKeRqhluX6OmieoYp0naSVIDtc=,iv:E63XJWCk8BJVcG1wBLwOD94WZSA01cJ9a7yTqQ4JRzQ=,tag:ZeEfbTNHxY3DYdeFqOQ+Xg==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:jWkBuUuYPibDzlVk5JeVbpXlV9kCe3IPnxNIZha7RHo34MPkQJI//g==,iv:ULZOabRRRh8J9TgxQn1ROHisWSqB4yE+5cWFnuCo110=,tag:GQudDBFT27GX9HOX+D/ypg==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:+tMZN/aEVGdjaSNKMSb+EiNwMmU=,iv:KbW+t/wtWic33FbgwxQj9wbIoQ0da9lqkw+q+m3NQjE=,tag:T07bU+ldA61JgyUho0cwjQ==,type:str] +S3_BACKUP_REGION=ENC[AES256_GCM,data:93FSzI9fwJXfJg==,iv:b65GI0iS41kUdHA3AAegjmIwuoXnsvzsIx5hhEx1Qc8=,tag:5cCojoNSoLZJk+KGHMBiIg==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:q0Xy3LeUs1CDKQNdrGEMiJ9Sf9pNiM+eA+CybYPuBde+En0=,iv:61/hic1jB4b5Mn2Yhv7NlfzjyFcGlpD2dYd06MI6GmU=,tag:2wN70cVd6m6S4UcJED9wXw==,type:str] +S3_BACKUP_KEY_PREFIX=ENC[AES256_GCM,data:4xFx+7gK/MrmZA==,iv:DtoI3q/tXx06Eo4kPfaJNn9dF2Ln+oBK30TPdJ2JuUE=,tag:uwCZBIzfb6BYhbDbh2QPoA==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZeWxGbDNXQWplZGhIUXE0\ncW4xcW5lUExkcmFRNUpIZkVxTk1nQWxtczBZCjhpZlZqSTdhNGt3ZmNIcmhRRXRi\nbFRyZGZERFNpajNwVVh6Mnc4bStMSFkKLS0tIExpM1BIUzQ4c2NYQm1VR2NLeEtJ\nOEl6MkxXd3VGc2VaTjZNRU9OenJSSVUKeVep1soqdUYMyW2+PB37MWhMn7MiyEl+\nedntudOs2Elz6Ix65nTiKvj4S4uaRC6O2xRyEsxkWLwogXAHv9FAVA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2RE5PRGgzZThCeXZucFpr\ndXp6TmE1NzdWNmJQcDlFS3dEdzVrUFVyN0N3CjNtSEVWM0VWbE1OMnVTVUNtUUoy\ncG0wOFNlTklhek0zVmhydTg3bTJucHMKLS0tIEVnYjJXR2laQWtPUkhKV1hkd2tR\ncXpHamhxRHdDd0t4allUMjhySDhkZWsKXet5TcqZ8ZGhv14EPHU2JHEIFFBF7L5p\nmz3pbs8BIe0rdXewjyjmq7tdFFdyWxvxWl6bRVU0pkCKwl7R2GZWJQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwU0ZZSkhYQSt3YzAxOHk1\nc2xPODN6WEZlcTh4LzQzbFVtR3BZbUNnRDEwCk85Mm41WEVaUkdTZHdiWUM1RzZw\nQnFNVHdUNnkxOVJ3UEVRc0FNL0lXV3MKLS0tIFh6SEVua3k0TWFGMnM5ZFdGaTdw\nYmd5OEdvdFVDR2VldjBlYzRNNHY1REUKQIE3+zDgBRZtZhSyTaRuNTdvFZUxrb7D\nbU6ZVIaA7gSDH+CNDybkU3Pam6BHunNYuM3CdpfuMxDxWtDS5xtMHw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArem03ZXVNVk8vSTM1QzFl\nWmFRcHc5dituTkhuR3JJWmRUV0lNa29ZNWpjCnlpMHVQY1IxcUx4RWZPdjBoSzBw\nckNhMEFSZk5hd0xQNXZWNnNYcUFIRWcKLS0tIGpwZGhBWk5tcVhTVC9vNWJLSko4\nSEpPZ1ByZmFDUER5N0hPa1pUK1pqNlkKjEBDR/6zg0muHZdD6zDxut9T3UN9SzVB\nSXChIYlJY7I6xVvPvbCRrwC1r3wPSZcz11B+BLK3QaWTHCFfJjQHTg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRT0pRUFdhc0hKeHh3enZ4\nNVRCWDNVVHFLVGE5QU1LV2xTT3FpczRGdXpnCkhjOHN0OWcvRldVWEFzOGlXNWpk\nQi9MaDROWVE4c2tBMUpZVThZbVZYKzgKLS0tIGo3Mm8rUXdVd3VVU0hZclR6dHlB\nUGIzeWZuSzA4cjlndXcrelNuZndLOWcKcjUKPLhCggDRc8Hb/tTmNAgOn1gnhnYI\n2Uhv6pMRUykseKvlcXnuBdC2QIOyTe9w/pV2qU93YQVgbFVQD1pMTQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGRS9YOUtVRk94azZTTW1R\ndkpNa3QyODNWYXZVcHhFVXczNlVydTl5VVFZCjFsdGhkcVk0UGhCUjVuMmQ0dWFL\nYkNKWXNlWnViQ08rZmEzUjErcnIvM3cKLS0tIFNaSjFaWDAxTDdkcXE4YVJzWVZB\nVnIwSjZSYk5SY3pkdW9OUjZZUUtYQ1UKUEb/Ln/pVq7btFL+mrJqGnYsv1Qvx/HY\neXDSLWZ6Rz2DKLZ8n0hSpKm9nHQv66LTBhQd91AZCRtVcO53k6SXeg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy +sops_age__list_6__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxeVVWQlViVUxIVXpmUUJX\nMmVFTXV5ZmJ0TzA1WU5IYnJicjRBUUZoWWpNCmRXUVA2YzNaY0dXY1FKNEs0aVRO\nNmtqL3lyT2hwdHg0WXpRQ0IvNC9VOU0KLS0tIGFiOFdOd3VrZ1hLcURZU1VzR0Z0\nbDNzd3BnOU4zeS8zK05PRzZ3aHZRNncKKU1NP0eYmZSt0Th5nJNDzzdtqaXv4ivQ\nElClGOYPmC+nbOUfvMpULP7ShqgfhUPSzTUalc1it5LdcM6zP6wbhw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_6__map_recipient=age15fsv503cl9ajfg9fphw45sfq0ytlptnr8wtlt48asytz4ev6rfks2pwt5s +sops_age__list_7__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBRGEvWngxeFZtSk1PdTc1\nZmpzQjJ1N1U0K1BwQ1hVUUR3aXNQSlI4bzBnCjQ0azBmMjQ0ZWpSMEE5cFZBY0Fx\nenQxQ2NyS0Q0U0ZLOERmQVd6MVpvem8KLS0tIE5Kd2ROVEpRN0hha3N5UXUwRS9T\nVUFhNkVJemorQ2J1Y1JNWklmR2Q1cVkK4yKCakmlhXtVxKYqjlyiMJoWYRr1woBH\nw9l2Bo67Gi50o3fm5E3HnfsXY29RBo4avifZmwoBSMa0GCKyPvzviw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_7__map_recipient=age14dssfufn8n0n7lcvyxxp4rq7zuf5pv0ek4zl9h7atsy85ceynfvqa0qz59 +sops_lastmodified=2026-04-30T19:55:53Z +sops_mac=ENC[AES256_GCM,data:2h0PhMongv6jOZU2QrIuz+MYwGFGVo4+OgzzOgfkBvo4EJj9CCuXe3E4kfIfHOiRGoga7WambuMCGrW+KRITEf0tUmya7ssCSQwzdkc5ryYE2RfGabILIzmXtH7TkylcftoJu3lrtZIaE/GloRbhzPLniriYVO7uQFEiY6GyKaA=,iv:eA04R328pNJQA22thQ1an7eLLv72edaq8Sbgz4NYNzA=,tag:UMQdPic51Z+763apbFQSXw==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.12.1 diff --git a/infra/.env.example b/infra/.env.example new file mode 100644 index 0000000000..aefd0306ef --- /dev/null +++ b/infra/.env.example @@ -0,0 +1,55 @@ +# production environment variables +# copy this to .env, fill in real values, then encrypt with: +# sops -e --input-type dotenv --output-type dotenv .env > .env.enc + +PUBSTAR_HOSTNAME=app.pubstar.org +PUBSTAR_URL=https://app.pubstar.org + +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB=pubpub +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + +PGHOST=db +PGPORT=5432 +PGUSER=${POSTGRES_USER} +PGPASSWORD=${POSTGRES_PASSWORD} +PGDATABASE=${POSTGRES_DB} + +VALKEY_HOST=cache + +MINIO_ROOT_USER= +MINIO_ROOT_PASSWORD= +S3_BUCKET_NAME=assets +S3_ACCESS_KEY= +S3_SECRET_KEY= +S3_REGION=us-east-1 +# storage endpoint used for signed uploads and server-side s3 calls +S3_ENDPOINT=http://minio:9000 +# optional public endpoint for generated asset urls +S3_PUBLIC_ENDPOINT=https://assets.pubstar.org +# if hostname matches S3_BUCKET_NAME => / +# otherwise => // + +# private backup storage config with separate credentials +S3_BACKUP_BUCKET=backups +S3_BACKUP_ACCESS_KEY= +S3_BACKUP_SECRET_KEY= +S3_BACKUP_REGION=us-east-1 +S3_BACKUP_ENDPOINT=http://minio:9000 +S3_BACKUP_KEY_PREFIX=pg-backups + +SITE_BUILDER_ENDPOINT=http://site-builder:4000 +SITE_BUILDER_API_KEY= + +JWT_SECRET= +SMTP_HOST= +SMTP_PORT=587 +SMTP_PASSWORD= +SMTP_USERNAME= +GCLOUD_KEY_FILE=xxx + +OTEL_SERVICE_NAME=pubstar-v7-prod +HONEYCOMB_API_KEY= + +API_KEY= diff --git a/infra/.env.preview.enc b/infra/.env.preview.enc new file mode 100644 index 0000000000..46ce1d2921 --- /dev/null +++ b/infra/.env.preview.enc @@ -0,0 +1,61 @@ +#ENC[AES256_GCM,data:opvA3YzKiY9Q3J+ZM6oW9hJiW9X4oD/RxvpObZLFkT26,iv:hRFVlcoRJXZpw6TKWdwX0zkcSmMtuAKrhpLsg97aWuo=,tag:dApblQ7vruBMJEHHIHWBng==,type:comment] +#ENC[AES256_GCM,data:hL1rGGSqDZ2k+A6BCPCYTNNgGctAMOvkTp/H/qurxZYXmapGl1uTdSpMREHI+8bTxEVrLrAM8gFGI5U=,iv:FbYksmvf2FgkislfMovwSLcUQ3lJzuGECMA4fH/5uNc=,tag:OmN1wwt5Uew44G8esTYCaA==,type:comment] +#ENC[AES256_GCM,data:udKBSgtC6FDhfTc7YUg2LLRBwAdNOv76yo81FR6rycOW7NkuIYEUszsock14wg/IdPGBOgQkK1Ds157S1Or9YdZfeA==,iv:SCe1tsmsD+CmwhthsR/7LhE9vEWToTDNQ7VzvG+GxkM=,tag:JzBRa7C9HYmSmL6hQuAO/g==,type:comment] +#ENC[AES256_GCM,data:KmWVGKc64MGi3pD6W7TAYQ5nOHQO74Z6c10aH6SllYuYvD4Mwav6QF8X6O4xl2a/4V8=,iv:7hXbLDP8lGsteIhGciSKfBFwQE+KAQXHkSGceWsKOlQ=,tag:b9iW6dhBe+JowUyH7yHVug==,type:comment] +#ENC[AES256_GCM,data:t8elkEXgI2D9JvTfnDjtpDyaygUFPA7sQHIpfmLFXDD9iUS/jAFkO9fbB0JxiMuOFBwLeyQ=,iv:3y3LfpOcBiQBxcfMNt4WNYoDBd0tKhOmonY/Ef7w0lg=,tag:XA6C6KBJMvdRBFS90uWKYA==,type:comment] +POSTGRES_USER=ENC[AES256_GCM,data:K5/C0OPA7fo=,iv:jAqysnPFM/17JrmT1nqrN2sjorLS5vGaujK+Aa3zbVo=,tag:8qSkSlOCiw7lqLkXw9pqNg==,type:str] +POSTGRES_PASSWORD=ENC[AES256_GCM,data:qAKDEuU3IJo=,iv:lI70KKLrUTOoDE7YIEarWccL+f4b3pLhY++ivvac8vw=,tag:ixASoMqSPNt555Y6Kea37A==,type:str] +POSTGRES_DB=ENC[AES256_GCM,data:0BZzxBeqLQ==,iv:LFkNxjIud4TJyOi5jMRL59IPBumD1Qq7++Kf/G55v64=,tag:/Xy1GTnURkyjDNWuKm6F3A==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:ISovuAAttgefrbN1B66372Vk1izYOZCjhy53EqpcDkaOT2YswviaLoVd6EOt0A==,iv:IUs2mqHQtNP2el/54mrcePbkPmwA58/VQh7yaUONW2o=,tag:7a9+noTpXFhB0bBZM9LYLA==,type:str] +PGHOST=ENC[AES256_GCM,data:1CA=,iv:3t+kg+XwspXavRqq2/hKx2oG+KoePZrLnVnVIHwI3rA=,tag:mG4t38Faqb4r8HP1GhxJ/g==,type:str] +PGPORT=ENC[AES256_GCM,data:SlCYxw==,iv:lprPPFD/RlmwlwzqfA+qLweWM4xj1mDiF4DrCLU/77Y=,tag:f5E7zYDcwsOoKszK9czHig==,type:str] +PGUSER=ENC[AES256_GCM,data:ELrq1plLYFA=,iv:yuepQOHV4U0/ppGIwbJxiF2Eise9Vd+2cXonJvyg/jg=,tag:S6vEFmrXqtb9SQqW3DOj+g==,type:str] +PGPASSWORD=ENC[AES256_GCM,data:HavMtes7Q1Q=,iv:fiukPac5Td8egzcKBlnvFXwzD4v4dSY6vNX+yHYPSiM=,tag:IFALgtKtTE/ySicH/VUdCQ==,type:str] +PGDATABASE=ENC[AES256_GCM,data:wjqyXAoG4g==,iv:eE8CfUrzk0kHPioAkpOTMVWZqe3Dypa2gmdtXDlj4TM=,tag:CM1CRsgqTCop6CG7X6OIog==,type:str] +VALKEY_HOST=ENC[AES256_GCM,data:X4k/MLA=,iv:vrdghPqjnhgOvmpleqbND3O8EyMnWI3U1l7XolLi+eQ=,tag:nY73oTSJR/woHshWJPtzvg==,type:str] +MINIO_ROOT_USER=ENC[AES256_GCM,data:sCwKQnsZ+DIIJQ3ajw==,iv:27MQR3JzcOWySVvwXQwZpMu70TCb1k5Z86rR0zXRZdo=,tag:IQpAp1bWhHcQ79MbFfpj0A==,type:str] +MINIO_ROOT_PASSWORD=ENC[AES256_GCM,data:JM9y7JW0hiBEiMHbgw==,iv:gg9n38GeRhNzlBBBNNMeMTHVR3XeqbQz3TOPIWl+0Rc=,tag:nNg/o6R84l+oogLwSSEOZw==,type:str] +S3_BUCKET_NAME=ENC[AES256_GCM,data:ngLxQd5kdOZ2hAAGSOM=,iv:hAD9RzivHCRHYuA2cDbYOztQcyIHHWGbA2/1uQPDS0M=,tag:0pzlIXiCuzzG+SstQzlkvg==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:qszf+CCC7IcyBvA=,iv:tKy/0OVu+89nedQomfXPOqr7q+RS8HDZxNTDiR6pS4g=,tag:Imv+9+ZfXMdNYQMYLgau5g==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:mi0qfVGQjWdyuNI=,iv:5tvbkHfViJmgEWFfUzxz7gaEngBcbVCprSk/SnukoNE=,tag:0WgrCx5R8hnB48pu17Ll/w==,type:str] +S3_REGION=ENC[AES256_GCM,data:i2h62JmaUcud,iv:LGY09HJCLE8EGY0CZKJSXtmayEKxantndiV3fgFuNis=,tag:pTPctxJvatCZKfEb+gVH4g==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:HO+AZ2jcAruVnKD0TxNDkvA=,iv:unmrLW7S76UwvYtHNnWgs183Cpv8svCHw2qFDAYS1U8=,tag:wzqWNbz+apNXycM9HNM2OQ==,type:str] +#ENC[AES256_GCM,data:FxM6a+BUZqsVlFHLP8win9OWeOJGZMV20qseDC3Zt7nRJRv8LIfOmwIC6FlSTU6bsHSl6HAc+edP+EUq1E61CqtXlg==,iv:kly7uEx2s/sTXYfdRSi6CeZNzOGaqqiCzC/1AsRC0RA=,tag:wD1zhDWicWWQvTxe4j794Q==,type:comment] +SITE_BUILDER_ENDPOINT=ENC[AES256_GCM,data:ZRW1tLh1F1QRAWe6UbJbupKBYlyO3TaK,iv:zIYnWHr4XgEFEUjJR6HFNNxi31mLI5FjFl83o3x8D2Y=,tag:W8XacvYDXRyA0KNC5uSL6g==,type:str] +SITE_BUILDER_API_KEY=ENC[AES256_GCM,data:wTBd,iv:G/cEVmGsm7AHjoCldhowRMdnIJC7LoVIStVicsgGkVI=,tag:t0WQRO9VMNi9vlFK7Ga9kA==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:CWimJRwOMGA=,iv:dpxpDzTrQEsMLO47gGsgGtbV42e7BTIrh9sNO6qZf2M=,tag:kPwXN0eqcV3ygbb8vZt/og==,type:str] +SMTP_PORT=ENC[AES256_GCM,data:vYzVwg==,iv:yT4d+AV+Ik7yMdWLp2z0BZJFZFKzB1bUAYxYpfKcH7U=,tag:+DydEtLtCb7f83TC6l+AnA==,type:str] +SMTP_PASSWORD=ENC[AES256_GCM,data:TbSj,iv:RlhlJtf9gBqlKLuirIH7AJfoG9CBcjsTvWkHgErfUP0=,tag:srPIDZSeGL+pOe75iRHOAg==,type:str] +SMTP_USERNAME=ENC[AES256_GCM,data:ayNC,iv:F63iWmQg6wt5CMib97Yjrd4QDNTVxvTIKikz6IkTlyo=,tag:C004iR/Oqwh8d98tkZD8Cg==,type:str] +SMTP_FROM=ENC[AES256_GCM,data:joi3HeS9+oXmPik9M3Tu,iv:O25qj8hu5OgDoAmmek5jWGmELqBtNtCBa9Mr8Ai61i8=,tag:oxUgRTWgtGrswH1xgHe2qA==,type:str] +SMTP_FROM_NAME=ENC[AES256_GCM,data:zF4c/sQ9hPZ/lgf1,iv:2p+LiGn8ParfkX1yKVenaMrGiImH+F32lCm5ufvcKk4=,tag:udYB9Eiv8OvpYxUaW26u1Q==,type:str] +GCLOUD_KEY_FILE=ENC[AES256_GCM,data:evFr,iv:mXlpTcWz0wtYFq19YjY0xJaBkUQ3xlB0LmAtBVyaIIQ=,tag:JuvQqEklFspZzVTi5KpvGw==,type:str] +OTEL_SERVICE_NAME=ENC[AES256_GCM,data:wkW0PXdD5iWlHYkRFC8VyjCp,iv:/mNLe6/E5xspOqirFXtkd1TodZJOaWgBiSh13f5oGp8=,tag:hylVwslWgVEZNCVddJFYwQ==,type:str] +HONEYCOMB_API_KEY=ENC[AES256_GCM,data:zlwK,iv:+B/Fn6g44IyMC7yJO0RjUHUSXtouhTA8JJt/eOZ5ivw=,tag:PAomYMA72tZfC9wT/D+AXw==,type:str] +API_KEY=ENC[AES256_GCM,data:jB/x,iv:iurTVomel8n1ovwUwXdF357aV7cv522Bv89bDIVspG4=,tag:gOpmHWO5dIZ+ORiknqGzPA==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:YDT7KwOcriGgHTc5g+2GBUkP+MTk,iv:4YM3ajBuheMPh6a1sBeJxsAK6YodG47lBWvuHng1peA=,tag:ZLvMIyfl2v81w+JDJNpYyg==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:eHrqsWYibdbo0KAUz+YLp04=,iv:91HsG3+AdoVVK2ubnQ5z2mkzwIzUQCQsEuWR2dqv3mI=,tag:FoN6epp8lAPT1pI/wHzy5g==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:w0tz2DavM5LraGApdpB2etA=,iv:RelXs3VkqAe1hMQniAAkbyHgE89DGVMDhHmGFt3Xdpw=,tag:qCSitbzUY+MiVPw8nnCNxA==,type:str] +S3_BACKUP_REGION=ENC[AES256_GCM,data:xaE3xXFo8hQ9,iv:Bvk0L46fqwCn2zMwbBb0TPgq/BfGT1eTIlEndtgwMh8=,tag:3gl22oDsiZawkl0YGMKT7Q==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:yqMRh+eu9R2WoGXA+W4wEO4=,iv:a1PIgTDOUQrhbpGM1yhBOVXUNgHvxxSupu9hLBEUi8E=,tag:M8KAhT9mf50LayVDBpuaew==,type:str] +S3_BACKUP_KEY_PREFIX=ENC[AES256_GCM,data:TgYfURFv08hZBw==,iv:FVfXXL8TpaPIXUWhj5oqDJsaR2iOmp5quV/dU1AB/go=,tag:SsMiCHjWeO+b1fgMTQf1Lw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUZTdBcS8xekpMSjN6bElS\nTWc5OW83bzRFS2dDRFJidWR5eE5yQ3d4REgwClR6WEdRWHhRWWlTcXZUZzBiRith\nMU1KZmJXb0tHWThEeDhaTitzYU1hRVkKLS0tIHd1TGFtSXI0eElGVVdxK2t1bDhY\nNFIyZUU2M0gvTzhuQlNDbHBTNWxjSWcKltahYQwh7DyrcoVBt77mRHAqj1XzJ7Yo\n3OylYMJ4xxkqEf3gwTTwzhCuiClXzhr8Bau334O9popXSYFTCU81QQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuZVFvb0k3WndabHcyUmd1\nSmYraWpQZ2U3NDJtWVhWUm9qVGkxeENyaUV3CnY3K05lSHhwWTNFV1JCZGovUWV0\nWlQxVjNncjlEZnhpUi80d0oxem5jREkKLS0tIFVDVHdLQm0rTk5WVVNMU2MwSEJ3\nVFh1YzE4SlF2RzIvN215Q3RBSzh0UVEK3X/JLGF/3gue6eMzxID9Tc9HIcR4SRls\nJ3E2NWNiNc5lCr5Pp/SdlUGrb7s1cs7n+uWIhsFmdsf8wN2JfFy+TA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTT2J6b3VSSXY5eHNTYW5H\ncjFCTndYbktSVW9JU3RNd0NML0xLc0RlaDNZClBBODhxQ01CODU0SkJaVEhwSlJ6\nRDRYRHVaMDdwU2lWeS9iTDNsTGk4a3cKLS0tIC9PS1REMnVNZlk5bFB2dVI3a3Fa\nY0dwSU5qRlFoalFzdHExMlpVQWp5Z0kKpAR7j2UL/QEpNQMYgcobP9lYN/zv0Rv1\nval3yv1MNtJb08Gj9D71Wm7N/UMFqtpQElJsovfqnyyX0/VliuNSPg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwZi9IMTBhTmxRZWdUQk1k\neXdCQnBXZTl0ZlgyQ2UzSkN2T29GYVB5V1Z3CjBRdWsrbktvUzVKanRVVFVwcG1u\nRjM3VjRFRmtPMkMzNS9XWENoNnFKSG8KLS0tIG9aUXB0a1U5R0pRVnpGTU1Lem5k\nTVRVaVBQZWtlRkhrSFJjOWt5SGw0Qm8KYsxHiwlQSm7DlRFJqf5GpSafdF7Hb5+5\nYaK2r9XOixfsLWsWLeyi+6/9p7dRYFj4cfmKMGgFTLQRtJC3cPdpRw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0ekZpSEtjQWlINlBkeW4r\nd1dXd2RYTW1pS0NlcEJOZ3Brb24vcmhPZkJJCmJ0aUFYZ0pyWGY0OGlxbDJ0NWVr\nV3RWeXJJVm9JTFZZQTUrL3ZIdTVmL1UKLS0tIFlOcjBNQ0NURE04K2lQeGZaeDBF\ndjVhSDZWVEpXb3FxbWszbEpDb3UrRkUK4Ih1exUXjTwk0r8DGxzL+1Fp7S57GpFZ\nbsJpNNgsIXvIJOb2EN1Fcyqqx9ZqsQsdTDEV5yui5xYIVgHiHaAKlg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaWdiY0xrQjcwQ2swRjhB\nczc2ZmMwaWR0OU0vZjNZVGdtYmFURGYxMW5ZCmxBV1RyNW5Hb0g5YUk3SWp5ZlNX\nb1ZvUXV1R0FBbm50dE5xL0RNSHFKRnMKLS0tIHdvbTFQM3Q4a3hmUGdLZkZWZjRR\nTExwTW1Ec3k1VkZwZVpBNkh5L0x0aDAKGZy4HGjE2IwTVxtVoLBvVyd1Qry8U2Za\ng2yluEZBtdKDPzi1XkG5xMUzK4EY+Gv/6I8Vvz85bmpgusGTRhC8EQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy +sops_age__list_6__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBENFBDdS9IVXhEQklBN09F\nOG5kZUNGSFFacW9QamJ5SkNsYUdQL0FOTHg0CjFFVUsyYnFNcGF6dTZPUjQvaTly\nclZMMUZraDhpL0tCZzJGYjMzalJlSmMKLS0tIGw1NXFBaU9BYzloOGdZSEpjM0xs\naHc5N0kvZDJPMzdRVldDMWpmc3UyeGMKtQfiu04UdnQPUa39wpyjcwfEhp0jUZGx\nqcvx36r4VXXVr0VyYYD47RXI5JVffDmrmqb6nkC57P9XoTGeDrq0aw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_6__map_recipient=age15fsv503cl9ajfg9fphw45sfq0ytlptnr8wtlt48asytz4ev6rfks2pwt5s +sops_age__list_7__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtZzVPVHhhQ1VKLzVtZnJ6\nc21JSGxxSUJqSlVONE1tTnJJWVBTczZvTmg4CmNVczNxUmdhWG94ZFlSQmhRcWQw\nTkthamY3L285anVCcmNpUlNZdGJyRWMKLS0tIDMvYkhiMkd1N1psZzZxa3NXbDBt\nOXJTL3dDVVJrN0w5Y2JLWXVaUEpxVncKpeZY55h79/76jAEvsWUSROJONWZgA4YN\nCVY9D3kwN1u8xmLLHbUc47FIAfAMHUGZl2aqJyT9D4Nn01cEtwj+5w==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_7__map_recipient=age14dssfufn8n0n7lcvyxxp4rq7zuf5pv0ek4zl9h7atsy85ceynfvqa0qz59 +sops_lastmodified=2026-04-29T14:59:32Z +sops_mac=ENC[AES256_GCM,data:0IGfy2z1rd9AC2g+dyfPynR2nxZHBm+2I48rsFk2S6D0pe6L1k6M6htC22wQkjX6kSePVbEtS24QTS59/OP74HMPDHKE/YgrUYcxqjwAtr5b9E5DRflO+GMJpDS48WO1x9qTNA6AJkfHIdJ5h1nGPSS1xIcgdZeG96B4VazldgE=,iv:iDP5HpJNiI9cgGkP3ehptEEY4xVQsyzLNr8xuUKJogQ=,tag:CmuKRRlzrtBeQD40wTm/5A==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.12.1 diff --git a/infra/.env.sandbox.enc b/infra/.env.sandbox.enc new file mode 100644 index 0000000000..e676edc502 --- /dev/null +++ b/infra/.env.sandbox.enc @@ -0,0 +1,61 @@ +#ENC[AES256_GCM,data:tF+NltX0H4lRZhaiAXufdjxUXI6mhahYZJ0a+yLnMfBe,iv:gPG3i+H/MHfIqNkXhTz4/qniEznMTOkkWuBbPJARQTA=,tag:ZODl0D8JpS1kFckbruMVEA==,type:comment] +#ENC[AES256_GCM,data:EN2pyGpFNFSbIkWr2AnLnD2kMSynYkiqnTLEArUVN41JA1mEGmm2pwkfe05M96Ek9/c9OWrSuC9wiZs=,iv:abLSNzneVJZW3MU4M0xTE5wtMInubGRiXmOAKnPV40o=,tag:wgJRdwwbeYkpNbsWwSOwVA==,type:comment] +#ENC[AES256_GCM,data:bziwAg1j9EronJ8qWD+gaH9wafAXUlWJZBXb3kuxE7ebov+/2whAamyHCFJclI5WqHjiIS9rhxIE0GvFRxIbhGpcRQ==,iv:mchtIFdVGpPd8HfEk7AwreZpze61IrMhI7H8ir+t4+0=,tag:Glp5g0pcnQ2NysiGr9qvpA==,type:comment] +PUBSTAR_HOSTNAME=ENC[AES256_GCM,data:oABFln8WFDncQTjkJwBDASONnQ==,iv:QljTPv9UV8I23E8r2wp5Uj5sEXhG6znb/R25+e5gNHU=,tag:n2pha9MjnUB49JxAa4AeJw==,type:str] +PUBSTAR_URL=ENC[AES256_GCM,data:Ntj0Tlkx2mZ1PTA/gCFqnxclEjtm9pSiQCrZ,iv:8u698h1GcZDhwgLxkx/DQMbGsk5rKg4vfKjv6KvWvC4=,tag:NviH+AeKs/ZsCbCpac3WBg==,type:str] +POSTGRES_USER=ENC[AES256_GCM,data:Rm9As1s20Lg=,iv:+svOJMv5QnE8HW28ILiYdzoXU92T2igJTZgJ1azAzAM=,tag:V/IzKe5u1QVQF8uoGWwzPw==,type:str] +POSTGRES_PASSWORD=ENC[AES256_GCM,data:omibGjCYIY4=,iv:XiQI25BV9Te8hgRAlqCpYWyG14V2xk0k9sVT6sRoqxg=,tag:1BMdjBzx6psCDjBfC7oDIQ==,type:str] +POSTGRES_DB=ENC[AES256_GCM,data:GfLYNxXSoA==,iv:/EYawF79c2nVd7E2bd5XpZF/JvKmqCuw3450ec7thwg=,tag:XLbc8WpUHhuZ51E6AiJS4g==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:WEe9K67uUkNPvrCUq+nmMJFGByhKvDqEO6jRWOP3E+Mp9UdYYZzdW01idLkwWA==,iv:GwKaKmObHUnJxT/Dt3IcF7kZs+9H1irgmDxMppgemvg=,tag:y2XlWG56mHfmjcSa97oiUA==,type:str] +PGHOST=ENC[AES256_GCM,data:SX0=,iv:bOmUUbSDRNOX1wYv/4QG4Bi1UvTL4okiGepeTRPoHeA=,tag:sdtjbzWcemb3GSjImGikIQ==,type:str] +PGPORT=ENC[AES256_GCM,data:1ZTi8A==,iv:typ1xzPft/qeQd3pXCNa/3x+CGOOIM0Wb6DzAbyzvjg=,tag:yQs2auNzGXLFBS6NyhnfSw==,type:str] +PGUSER=ENC[AES256_GCM,data:L5H4xna9NoI=,iv:CGRd6H8Ao9zPFmNZa/nAZnI3c4luxvIJaX7293sBat8=,tag:3eL+IlQu506bJGOccbIEbA==,type:str] +PGPASSWORD=ENC[AES256_GCM,data:c2EHtIGCY7A=,iv:kt1Yn1aiRsiRJSrJ0mofX0YtT4tREq5xIHtP7He9AQ8=,tag:xwqzc73O7PMcHAuyDT/2kw==,type:str] +PGDATABASE=ENC[AES256_GCM,data:i94qQTr+AQ==,iv:PWQF+ck7b2Kuq+x/RZKxRRo55kVtGktJK3z2NmJ+MhI=,tag:w2OAZvJLfpwSctNcOxW8NQ==,type:str] +VALKEY_HOST=ENC[AES256_GCM,data:2CDHsec=,iv:wVNaNQri6rNfW+kO8CAcnmTrVG6z6IKNp0P9ybwBSG8=,tag:BXbaxshzZgT+a86MQD8tUw==,type:str] +MINIO_ROOT_USER=ENC[AES256_GCM,data:JcT8s87Bm6DjvC3H8A==,iv:SdZQ6u1G+i/tc/cC/nsd4o+LRfdCQQZy7I22Z9KJrfY=,tag:RXyE27OUKzfjq5lyLWd1BQ==,type:str] +MINIO_ROOT_PASSWORD=ENC[AES256_GCM,data:OcniH42f9zFzqWrfKQ==,iv:hGdpVXbq+qqMP2+B/Vup4oUD2HpExNUD6oie2q5CJkI=,tag:n3PNhPYAC9jgMdXza+YDUw==,type:str] +S3_BUCKET_NAME=ENC[AES256_GCM,data:akRzgwxFWktUm7c44Qk=,iv:UrRsa4yPo10+OWeAEcTEUy9VR9Ah70fJeGbgEyVfob0=,tag:wV+4SDm7A9fPHxZ2H5h1oA==,type:str] +S3_ACCESS_KEY=ENC[AES256_GCM,data:KWdo4+7ftT+hcCk=,iv:He6FRP7moevc/vvBiFgBUvGU5I9pvA2ADO0kYVNz0hU=,tag:uVazhycoyXIkO2rerVZibw==,type:str] +S3_SECRET_KEY=ENC[AES256_GCM,data:GffjT1VJqHbQcKQ=,iv:IHadm2kgehENsQaHy//8BMbu8iYbZXrCOjp2fREQtr0=,tag:5LT9VaxPvtAtYRDEy3SR0w==,type:str] +S3_REGION=ENC[AES256_GCM,data:8u4SarTCdTcn,iv:TICaxtJU6wD3Q6i5K/ETOpLgON+eQIGgTNU9eMFMRDA=,tag:Qp8r6JuS+Y6TYnDul+wtHw==,type:str] +S3_ENDPOINT=ENC[AES256_GCM,data:cCR7s5MwGLhtTXm5zaRVTTM=,iv:aAUb+FWKBnsNqH6jytz9REF8qLKe5j5LMc7xF+Jrpc0=,tag:NvjuaN83rxAh5J7kiynq/Q==,type:str] +S3_PUBLIC_ENDPOINT=ENC[AES256_GCM,data:IEYZXG2hBkOQU1DmvEBQWpdHlGLu+6YW668cALMX/QaoVw==,iv:EVrM3BI1R1F23LeYQh1zNS2abESlAu7LjJx4TGdDYms=,tag:1BGPaOfYHRJb/xOUfl1ZEw==,type:str] +SITE_BUILDER_ENDPOINT=ENC[AES256_GCM,data:PNJApXvbgf5wIvGlzutr1rUwJqOHTyAp,iv:tCvJrso+5kPr8MBp3Bzs6XLR60vBjJ2l/lc8wl114b4=,tag:m3kr6BQXJG0EjsHSOj9AGQ==,type:str] +SITE_BUILDER_API_KEY=ENC[AES256_GCM,data:X2jn,iv:prOGWQXw75Hz0e80aVaKGa1gD6Z+MEESgLxn0wLHXaw=,tag:NmRkYoslfXueRKzEYh9DKg==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:uVvjHMdx8uk=,iv:jPVojBeb9J5mwOSzpB+PMh2QoaJOwmIl/02kuGLMIvQ=,tag:tzB7BgzdSE5AQmUkYzH1oQ==,type:str] +SMTP_PORT=ENC[AES256_GCM,data:uiZgUQ==,iv:7wx9UTNhNlEtXWRLvBUMKjS/EKslkKvYBdyFf6B2SkY=,tag:MEZjJAVbuDBNk6QE63tHTw==,type:str] +SMTP_PASSWORD=ENC[AES256_GCM,data:OQTj,iv:FY6HLczXg9rHVr3+Xo1kV+7FByT/FVXzy/4rRLXSpvk=,tag:nkgWdiRt+b511JFc/TRHpQ==,type:str] +SMTP_USERNAME=ENC[AES256_GCM,data:q1TV,iv:UJeONciELaSSPxT35qWCdRK+ea1hHCxmQ+sQ+q2LH+Q=,tag:kaxTB6D+yUvUuw00HoFRpw==,type:str] +SMTP_FROM=ENC[AES256_GCM,data:HOc3HI787OLj4OXr662h,iv:HXkj85ffIMmLhfWzpcamgmLnxywvSl24DepDJ/MtPpY=,tag:sWmDxISKvVfbjq/Y+++5/A==,type:str] +SMTP_FROM_NAME=ENC[AES256_GCM,data:Cxnc64JtRCy/CHo7,iv:N7Iv97ZWDuv/myyLQykPB7qxgcXAd9SP8JMenHGa0B0=,tag:rn1KneMaXOO6RBOLM90S6A==,type:str] +GCLOUD_KEY_FILE=ENC[AES256_GCM,data:fQzh,iv:YLl0RrAvQadpCTaGkqNIa1BvUk5A8pwa1+WWv3y3D0Y=,tag:78YM+o87DPOInnXUa2F7AQ==,type:str] +OTEL_SERVICE_NAME=ENC[AES256_GCM,data:nRuRyQb4WSTfcVYvDsRxseex,iv:4CGO42FS6URtMZQgsMeKS3vv4w0lIJr9vNlIhxGPKj8=,tag:4EMAnRAgB+mmN5ianNChGQ==,type:str] +HONEYCOMB_API_KEY=ENC[AES256_GCM,data:8wiA,iv:c4vfsljDCF/CYBS9xupkNqtvtqc+n6ZyfeNuwrWrB+o=,tag:rOWZxTbMdko1SlZSS1nvRw==,type:str] +API_KEY=ENC[AES256_GCM,data:iURB,iv:3Q6bMO2+AWzd2uLDtWaoSMc1xYu+ODLalxD0i1cjU3Q=,tag:Qvhsw+cuCyiKzF2jE2glRw==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:jpmctOMuie0ZG8MRq8j3bspXUQs2,iv:7BcTXO+nQsNbDw0c8UED31AvDVX55dx6M0zPhPDqTFI=,tag:gfB7jY201ywUIqvmhUcAYA==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:bkEE8tyr4c0Xfk6AOgFDjDU=,iv:hZuxjKIfMuicOUhofQGFYf6zySe3lnHFZ3mV6xDABWU=,tag:Pj0wERuVvT8yowh/n04JFQ==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:mjCtsdSn12RoBGRbTpvc0NA=,iv:PAQYzh7ESMPP5JfPtI4/auMMIJ2hy9iheRsF0DQyvvw=,tag:BTOSK1IeniEoXcXHY2lpeQ==,type:str] +S3_BACKUP_REGION=ENC[AES256_GCM,data:SAX9l+VtGSxY,iv:iGMZ5wi4Kv+PdhS8qgQdf43z8zvDVWkQWTa5aAjahJs=,tag:cvctAvbUrpX/soQ5ZG7jVA==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:tIPNOiQdE0cg1YGDrvxx+48=,iv:QBAAZCpC+tj5hfxmZV2XE8AaT94P73eacO2xk2iNb7U=,tag:JLLPwkEzI99siTaHrBB1dg==,type:str] +S3_BACKUP_KEY_PREFIX=ENC[AES256_GCM,data:V/rtkfWuQRF1zw==,iv:1/e+whCZhLzSW9hug/NSyAi9sGhV8yWWg7IYVCi8JKY=,tag:wnXvQJH/o7wklgi5BO9HCQ==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQaGlBQ2tBVFNZV3dRcGw5\ncnMyYUhnU3ZXSUtsazNNYkd4Skl6TytrYkJFCkNyVG04L0tlcW5naTk3eWVoSW02\ncmFjUTBMdlI3MWZGMXJDUm4wK2RMVXMKLS0tIE10aWYvbGZKL1FTWHBVWUpRQllX\ndW1PZWE0ZitFYTd4QUMzaHBCdVdnaGMKXsnAeUrceSs5Uw3WHjX3f0/cVcbO5FqO\n5Thdt77+6Vzin2WC0DHwsohBCfDRE7V8dGRzrkBEExG0ZxM0d3VLfA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3YWVpdnlwbmRHbU1TQ0RX\nL0pCTzJ1aDU4anRlR1I5Q1lSWFdzWnYrRnlzClBUbUxlUm9NRy94dWU5TDBMWVBy\nYm4wNmR3NEVKNXlHQXVUVFhuS0VJSmsKLS0tIExPYi9rYnlJVUZiUmR2TVlMdnh0\nZWs5bDlWVm1mOUpIaUViMk9uRklOSkUK0QzpgcLSb5SbOc/OBm+7Og4ZijiOh15K\nfqK8OKIbdvWNAvpnGwmD+TIgnKsOGHXHnwXaV2YEVBDeUxCKO5MWMg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxTXJkKzBLZVVyc0xUYnUz\nWGpLL1R4REM1Wjh1SmRRNDRINCtPRVBTQlNFCnJlbmxxQWpTUUFvcjRBNWs2Y1Zr\nVnRYaXRrL2NtMDl1cE1tYWxudnhIZlEKLS0tIFBuZGFFR2N5a1h4UFR1WFhEalp4\nZHZ0ZHVES3RLK1B6K2VHaDJBU2lkeFEKHbDh3f3Qhd6bGGhaP0s7fGxJAkiSiTzs\nS1e7GSw/vhV6bUIiTuqjzBhr6+js3cOAW5medMBA0shS8wgMDV3v7g==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXT2szemdTOU5IM1NLUEIx\nUThPMGpKUkJXUFNCRlkrWndQKytZdlVWdGtVCjkvOHl2SHg4Q2RFVm8vQ0ltU29t\nQzU1ZXVHN3dOM2tVMEl3VWd5cm96T3MKLS0tIGtXT0RaeHIzMEFpaHhORHhoSnZB\nSXVETHo5TFd4TU1hRkoxYlh4ZU5QRHcKaF9OnxmDiJRezJPNzq6dfVhynC/7bJ5q\na/uKNJSjkADy1ZoNbAIF1iBSXcq5zXX4gN2LAtuMapg8B3BVT1o6Yw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5OWhCdVNxM1VLQjIxZ0RY\naHkvT1FKQmZ4NkxrVXBmdDRSSjhiTlJrNlhZCnlZUitOZG9seHc3NGRxN21XOCtE\neGw5ZkE1ZHdwTUxiM0ZFTGozblowVVkKLS0tIDRlcXlnVmhndnJ1UUJVT1ZkN1VV\nbWNmQ2R1QlIzQWR5SnhKbVdzclVXcDQKfQ83Vc163IG2yX2SYiGeuifQB6i43Zvw\n7RgzcXVpq03OG+JJbDrugx8opSbaFzRjvTQYEwjzQC0v3mw3kj2/zw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhZG5ieDB4WDZEbEp4a3ov\nVDRRYndQU1FzQmtaRVgyUTREZzdiRS9uVnc0CkJLOHdvOTZRL0JmQ29OSlgyemtT\ndkFwc2hlVWtsN0luejFiUE9UZW1IN28KLS0tIGNaRlJBVlM4K3kzaktWR3BOOVlY\nYWl4eXpXN29VQUw0TllmVGsyZW9hbUkKgkPyA1rZxdq5mSWycBGbmZKzGvAYdVbo\nIznxZ5HuWBrgCKPpNU/dYXKBmTiI5+spp/Flv0ZwdDlNpB5zeXWA1w==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy +sops_age__list_6__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4dHhDWUgxTDBSWWV3cTFK\nSkcxcXBWWjZ6TURJclFzWGVnbFdHTTJqbVdzCm40SzN5SEx3Z2lGanp6MUp4SnVP\nbE1vN1I5UGlsUTF0NlZZRm1FVW43ckUKLS0tIGkyQmxpSWlua3NMbGl1cjltY3ZZ\nbUMxbHBXdnpHOXRLV3hrOG5ZbjB0ZWMK9zYALC7oV5PgWu5S1qaNVm6tnQVRciWt\nmmbYzMgr+dI5wk/KPGVskHA02G6fkbUXK/SvN5kyGzR2GOYrx9X7lg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_6__map_recipient=age15fsv503cl9ajfg9fphw45sfq0ytlptnr8wtlt48asytz4ev6rfks2pwt5s +sops_age__list_7__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6L2MwV2VmcW1KTE5nV0tU\nUkNyTjIybnJiS0N6ckQ0ZDlRMmlENUtUNzJrClgxNDBsTDRuRVNVNFp0ZEkvaFZt\nWkl2d1JkRWQrMnlhMFBEMXNyR3FjOVEKLS0tIDdaZ1lFdTJxWFJ2OXczU3ZLbGk1\nd3p4aG5VZHMwZ0oySzcweXlRaVhybGsK85Pk8mu/pjPcEPd4rlmQ7Z/2ytrflX5v\nIe6HCdX6VFVMJjLv1ymr6Cjq63XsRcArBfMqnq3lKy0LpMVJZE8IZQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_7__map_recipient=age14dssfufn8n0n7lcvyxxp4rq7zuf5pv0ek4zl9h7atsy85ceynfvqa0qz59 +sops_lastmodified=2026-04-29T14:59:35Z +sops_mac=ENC[AES256_GCM,data:FACFsFOpdW5jvRhLOyudN+glulntmoIerT7tI6j59+vgnBzDxMC0R6iIHRX0E4o0OS80GqXeUCC669dIsH8zqn6xMchVM2kQDd+zJocZ3Uqnc1AtudG7+hw+0RCVOceiE/+OGChR1Bz5E/vfPY3xOgcNWYwzchnRJFfdv8OV9bE=,iv:sfMOvKnIYIo2v1E9gvjZgIdPfA2saGGsdlyuH2Aj2Kw=,tag:HG47TvGh9VTd4nLIpi61zA==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.12.1 diff --git a/infra/.sops.yaml b/infra/.sops.yaml new file mode 100644 index 0000000000..155163bdb1 --- /dev/null +++ b/infra/.sops.yaml @@ -0,0 +1,18 @@ +# creation_rules: +# - path_regex: \.env(\.staging)?(\.enc)?$ +# age: +# # add your age public keys here, one per team member / CI system +# # generate with: age-keygen +# # example: +# # - age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +creation_rules: + - path_regex: \.env(\.preview|\.sandbox)?(\.enc)?$ + age: + - age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr + - age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj + - age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk + - age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h + - age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx + - age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy + - age15fsv503cl9ajfg9fphw45sfq0ytlptnr8wtlt48asytz4ev6rfks2pwt5s + - age14dssfufn8n0n7lcvyxxp4rq7zuf5pv0ek4zl9h7atsy85ceynfvqa0qz59 diff --git a/infra/Caddyfile b/infra/Caddyfile new file mode 100644 index 0000000000..d3ea92dd59 --- /dev/null +++ b/infra/Caddyfile @@ -0,0 +1,20 @@ +{ + admin :2019 + debug +} + +{$PUBSTAR_HOSTNAME} { + encode gzip + + handle_path /site-builder* { + reverse_proxy site-builder:4000 + } + + handle { + reverse_proxy platform:3000 + } +} + +:80 { + respond "OK" 200 +} diff --git a/infra/Caddyfile.gateway b/infra/Caddyfile.gateway new file mode 100644 index 0000000000..47d2f93351 --- /dev/null +++ b/infra/Caddyfile.gateway @@ -0,0 +1,75 @@ +{ + admin :2019 + debug +} + +*.pubstar.org { + tls internal + + # this is configured in the deploy-stack.yml workflow + map {labels.2} {platform_port} {builder_port} {minio_port} {minio_console_port} {inbucket_port} {mock_notify_port} { + ~^pr-(\d+)$ "1$1" "2$1" "3$1" "4$1" "5$1" "6$1" + ~^pr-(\d+)-assets$ "1$1" "2$1" "3$1" "4$1" "5$1" "6$1" + sandbox "19000" "29000" "39000" "49000" "59000" "69000" + sandbox-assets "19000" "29000" "39000" "49000" "59000" "69000" + default "" "" "" "" "" "" + } + + encode gzip + + @assetsHost header_regexp assets Host ^(?:pr-\d+-assets|sandbox-assets)\.pubstar\.org$ + handle @assetsHost { + reverse_proxy host.docker.internal:{minio_port} + } + + handle_path /assets* { + reverse_proxy host.docker.internal:{minio_port} + } + + handle_path /assets-ui* { + reverse_proxy host.docker.internal:{minio_console_port} + } + + handle_path /site-builder* { + reverse_proxy host.docker.internal:{builder_port} + } + + handle_path /sites/* { + @pathWithSlash path_regexp dir (.+)/$ + handle @pathWithSlash { + redir {re.dir.1} permanent + } + + # minio path-style: /{bucket}/sites/{communitySlug}/{subpath}/... + rewrite * /pubstar-assets/sites{uri} + + reverse_proxy host.docker.internal:{minio_port} { + @error status 403 404 + handle_response @error { + rewrite * {uri}/index.html + reverse_proxy host.docker.internal:{minio_port} { + @nestedError status 404 + handle_response @nestedError { + respond "Not found" 404 + } + } + } + } + } + + handle /emails* { + reverse_proxy host.docker.internal:{inbucket_port} + } + + handle /mock-notify* { + reverse_proxy host.docker.internal:{mock_notify_port} + } + + handle { + reverse_proxy host.docker.internal:{platform_port} + } +} + +:80 { + respond "OK" 200 +} diff --git a/infra/import-backup.sh b/infra/import-backup.sh new file mode 100755 index 0000000000..293453ad43 --- /dev/null +++ b/infra/import-backup.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +DUMP_FILE="${1:?usage: $0 }" +STACK_NAME="${2:-pubpub}" + +if [[ ! -f "$DUMP_FILE" ]]; then + echo "file not found: $DUMP_FILE" + exit 1 +fi + +DB_CONTAINER=$(sudo docker ps --filter "label=com.docker.swarm.service.name=${STACK_NAME}_db" --format '{{.ID}}' | head -1) + +if [[ -z "$DB_CONTAINER" ]]; then + echo "no running db container found for stack: $STACK_NAME" + exit 1 +fi + +echo "importing $DUMP_FILE into container $DB_CONTAINER ..." + +if [[ "$DUMP_FILE" == *.sql ]]; then + sudo docker exec -i "$DB_CONTAINER" \ + psql -U "$PGUSER" -d "$PGDATABASE" < "$DUMP_FILE" +else + sudo docker exec -i "$DB_CONTAINER" \ + pg_restore --clean --if-exists --no-owner -U "$PGUSER" -d "$PGDATABASE" < "$DUMP_FILE" +fi + +echo "import complete" diff --git a/infra/stack.gateway.yml b/infra/stack.gateway.yml new file mode 100644 index 0000000000..53518a6045 --- /dev/null +++ b/infra/stack.gateway.yml @@ -0,0 +1,29 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/swarmlibs/dockerstack-schema/main/schema/dockerstack-spec.json + +services: + + proxy: + image: caddy:latest + volumes: + - ./Caddyfile.gateway:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - target: 80 + published: 80 + protocol: tcp + mode: host + - target: 443 + published: 443 + protocol: tcp + mode: host + deploy: + replicas: 1 + restart_policy: + condition: any + +volumes: + caddy_data: + caddy_config: diff --git a/infra/stack.preview.yml b/infra/stack.preview.yml new file mode 100644 index 0000000000..6d6d3b518a --- /dev/null +++ b/infra/stack.preview.yml @@ -0,0 +1,187 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/swarmlibs/dockerstack-schema/main/schema/dockerstack-spec.json + +services: + platform: + image: ghcr.io/knowledgefutures/platform:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + HOSTNAME: '0.0.0.0' + NODE_ENV: production + PORT: '3000' + DEPLOY_VERSION: ${IMAGE_TAG} + PUBSTAR_URL: https://${DEPLOY_HOST} + PUBSTAR_HOSTNAME: ${DEPLOY_HOST} + SITE_BUILDER_ENDPOINT: http://site-builder:4000 + S3_ENDPOINT: https://${ASSETS_HOST} + S3_BACKUP_ENDPOINT: https://${ASSETS_HOST} + S3_PUBLIC_ENDPOINT: https://${ASSETS_HOST} + S3_BACKUP_REGION: us-east-1 + S3_BACKUP_KEY_PREFIX: pg-backups + FLAGS: 'uploads:on,invites:on,disabled-actions:http,http-allowed-domains:localhost+127.0.0.1+.pubstar.org,show-test-only-tools:on' + DB_RESET: 'true' + DB_SEED: 'true' + MOCK_NOTIFY_INBOX_URL: http://mock-notify:3000/mock-notify/api/inbox + networks: [appnet] + ports: + - target: 3000 + published: ${PLATFORM_PORT} + protocol: tcp + mode: host + healthcheck: + test: + - CMD-SHELL + - > + node -e "require('http') + .get('http://127.0.0.1:3000/api/health', r => process.exit(r.statusCode < 400 ? 0 : 1)) + .on('error', () => process.exit(1));" + interval: 10s + timeout: 3s + retries: 6 + start_period: 60s + deploy: + replicas: 1 + restart_policy: + condition: on-failure + + platform-jobs: + image: ghcr.io/knowledgefutures/platform-jobs:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + NODE_ENV: production + DEPLOY_VERSION: ${IMAGE_TAG} + PUBSTAR_URL: http://platform:3000 + S3_BACKUP_REGION: us-east-1 + S3_BACKUP_ENDPOINT: https://${ASSETS_HOST} + S3_BACKUP_KEY_PREFIX: pg-backups + networks: [appnet] + deploy: + replicas: 1 + restart_policy: + condition: on-failure + + site-builder: + image: ghcr.io/knowledgefutures/platform-site-builder:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + NODE_ENV: production + DEPLOY_VERSION: ${IMAGE_TAG} + PUBSTAR_URL: http://platform:3000 + PORT: '4000' + SITES_BASE_URL: https://${DEPLOY_HOST}/sites + S3_ENDPOINT: https://${ASSETS_HOST} + S3_PUBLIC_ENDPOINT: https://${ASSETS_HOST} + networks: [appnet] + ports: + - target: 4000 + published: ${BUILDER_PORT} + protocol: tcp + mode: host + deploy: + replicas: 1 + restart_policy: + condition: on-failure + + db: + image: postgres:17 + env_file: ['.env.${STACK_NAME}'] + volumes: + - pgdata:/var/lib/postgresql/data + networks: [appnet] + deploy: + replicas: 1 + restart_policy: + condition: any + + cache: + image: valkey/valkey:8-alpine + networks: [appnet] + deploy: + replicas: 1 + restart_policy: + condition: any + + minio: + image: minio/minio:latest + env_file: ['.env.${STACK_NAME}'] + command: server --console-address ":9001" /data + environment: + MINIO_BROWSER_REDIRECT_URL: https://${DEPLOY_HOST}/assets-ui + networks: [appnet] + ports: + - target: 9000 + published: ${MINIO_PORT} + protocol: tcp + mode: host + - target: 9001 + published: ${MINIO_CONSOLE_PORT} + protocol: tcp + mode: host + deploy: + replicas: 1 + restart_policy: + condition: any + + minio-init: + image: minio/mc:latest + env_file: ['.env.${STACK_NAME}'] + entrypoint: > + /bin/sh -c ' + /usr/bin/mc alias set myminio http://minio:9000 "$${MINIO_ROOT_USER}" "$${MINIO_ROOT_PASSWORD}"; + /usr/bin/mc mb --ignore-existing myminio/"$${S3_BUCKET_NAME}"; + /usr/bin/mc anonymous set download myminio/"$${S3_BUCKET_NAME}"; + /usr/bin/mc admin user add myminio "$${S3_ACCESS_KEY}" "$${S3_SECRET_KEY}"; + /usr/bin/mc admin policy attach myminio readwrite --user "$${S3_ACCESS_KEY}"; + if [ -n "$${S3_BACKUP_BUCKET}" ] && [ -n "$${S3_BACKUP_ACCESS_KEY}" ] && [ -n "$${S3_BACKUP_SECRET_KEY}" ]; then + /usr/bin/mc mb --ignore-existing myminio/"$${S3_BACKUP_BUCKET}"; + /usr/bin/mc anonymous set none myminio/"$${S3_BACKUP_BUCKET}"; + /usr/bin/mc admin user add myminio "$${S3_BACKUP_ACCESS_KEY}" "$${S3_BACKUP_SECRET_KEY}"; + /usr/bin/mc admin policy attach myminio readwrite --user "$${S3_BACKUP_ACCESS_KEY}"; + fi; + echo "clearing assets and backup buckets for clean preview state..."; + /usr/bin/mc rm --recursive --force myminio/"$${S3_BUCKET_NAME}"/ || true; + /usr/bin/mc rm --recursive --force myminio/"$${S3_BACKUP_BUCKET}"/ || true;' + networks: [appnet] + deploy: + mode: replicated-job + replicas: 1 + restart_policy: + condition: on-failure + max_attempts: 5 + + inbucket: + image: inbucket/inbucket:latest + networks: [appnet] + environment: + INBUCKET_WEB_BASEPATH: /emails + ports: + - target: 9000 + published: ${INBUCKET_PORT} + protocol: tcp + mode: host + deploy: + replicas: 1 + restart_policy: + condition: any + + mock-notify: + image: ghcr.io/knowledgefutures/mock-coar-notify-server:${IMAGE_TAG} + networks: [appnet] + environment: + PUBSTAR_URL: https://${DEPLOY_HOST} + ports: + - target: 3000 + published: ${MOCK_NOTIFY_PORT} + protocol: tcp + mode: host + deploy: + replicas: 1 + restart_policy: + condition: on-failure + max_attempts: 5 + +networks: + appnet: + driver: overlay + +volumes: + pgdata: diff --git a/infra/stack.yml b/infra/stack.yml new file mode 100644 index 0000000000..9ac048dcf4 --- /dev/null +++ b/infra/stack.yml @@ -0,0 +1,134 @@ +services: + proxy: + image: caddy:2.11.2-alpine + env_file: ['.env.${STACK_NAME}'] + ports: + - target: 80 + published: 80 + protocol: tcp + mode: host + - target: 443 + published: 443 + protocol: tcp + mode: host + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + networks: [appnet] + deploy: + replicas: 1 + restart_policy: + condition: any + + platform: + image: ghcr.io/knowledgefutures/platform:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + HOSTNAME: '0.0.0.0' + NODE_ENV: production + PORT: '3000' + DEPLOY_VERSION: ${IMAGE_TAG} + SITE_BUILDER_ENDPOINT: http://site-builder:4000 + S3_PUBLIC_ENDPOINT: https://assets.pubstar.org + networks: [appnet] + healthcheck: + test: + - CMD-SHELL + - > + node -e "require('http') + .get('http://127.0.0.1:3000/api/health', r => process.exit(r.statusCode < 400 ? 0 : 1)) + .on('error', () => process.exit(1));" + interval: 10s + timeout: 3s + retries: 6 + start_period: 60s + deploy: + replicas: 2 + update_config: + order: start-first + parallelism: 1 + delay: 5s + failure_action: rollback + rollback_config: + order: stop-first + parallelism: 1 + restart_policy: + condition: on-failure + + platform-jobs: + image: ghcr.io/knowledgefutures/platform-jobs:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + NODE_ENV: production + DEPLOY_VERSION: ${IMAGE_TAG} + PUBSTAR_URL: http://platform:3000 + networks: [appnet] + deploy: + replicas: 1 + update_config: + order: start-first + parallelism: 1 + delay: 5s + failure_action: rollback + restart_policy: + condition: on-failure + + site-builder: + image: ghcr.io/knowledgefutures/platform-site-builder:${IMAGE_TAG} + env_file: ['.env.${STACK_NAME}'] + environment: + NODE_ENV: production + DEPLOY_VERSION: ${IMAGE_TAG} + PUBSTAR_URL: http://platform:3000 + PORT: '4000' + S3_PUBLIC_ENDPOINT: https://assets.pubstar.org + networks: [appnet] + deploy: + replicas: 1 + update_config: + order: start-first + parallelism: 1 + delay: 5s + failure_action: rollback + restart_policy: + condition: on-failure + + db: + image: postgres:17 + tmpfs: + - /dev/shm:size=2147483648 + env_file: ['.env.${STACK_NAME}'] + command: > + -c shared_buffers=2GB + -c effective_cache_size=6GB + -c work_mem=16MB + -c maintenance_work_mem=512MB + -c max_connections=500 + volumes: + - pgdata:/var/lib/postgresql/data + networks: [appnet, dbaccess] + deploy: + replicas: 1 + restart_policy: + condition: any + + cache: + image: valkey/valkey:8-alpine + networks: [appnet] + deploy: + replicas: 1 + restart_policy: + condition: any + +networks: + appnet: + driver: overlay + dbaccess: + driver: overlay + attachable: true + +volumes: + pgdata: + caddy_data: + caddy_config: diff --git a/infrastructure/Brewfile b/infrastructure/Brewfile deleted file mode 100644 index 0dea01a981..0000000000 --- a/infrastructure/Brewfile +++ /dev/null @@ -1,7 +0,0 @@ -# infrastructure dependencies -brew "mask" -brew "awscli" -brew "terraform" -brew "act" - -cask "session-manager-plugin" diff --git a/infrastructure/maskfile.md b/infrastructure/maskfile.md deleted file mode 100644 index bbe9f89d55..0000000000 --- a/infrastructure/maskfile.md +++ /dev/null @@ -1,352 +0,0 @@ -# Infrastructure operations for pubpub v7 - -This "Maskfile" is the code AND documentation for common operations -workflows in this `infrastructure` directory. The commands declared -here are automatically available as CLI commands when running [`mask`](https://github.com/jacobdeichert/mask) -in this directory. - -To get started, install important command line tools: - -`brew bundle` - -Then you can call `mask --help` to see these commands in the -familiar command line help format. You can also modify the -invocations here when the required script changes, or copy & paste -the command parts as needed. - -See the above-linked Mask repo for more info. - -**Notes** - -Terraform commands often read info from the local directory, so the -`mask` commands wrap the invocation in a subshell with `cd` to the -directory containing `.terraform`; this way, if the command exits nonzero, -your current shell is not contaminated/changed directory. - -Both `act` commands (for container version updates) and `terraform` commands -(for infrastructure changes) require the AWS CLI to be configured locally. -Usually this means setting a file at `~/.aws/credentials` and `~/.aws/config`: -see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html - -`terraform` commands for the `global` workspace require write-access API token -to Cloudflare. Since this is one of the highest-security-profile accounts, it -is not assumed all developers have access to this. To run these commands, set -`CLOUDFLARE_API_TOKEN` environment variable. - -## tf - -> Terraform-related commands to run in one workspace or another - -### tf plan - -> Runs the plan (diff showing) command interactively using the environment specified. - -**OPTIONS** - -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required - - - -```bash -( - cd terraform/environments/${proper_name} - - export AWS_PAGER="" - if aws sts get-caller-identity; then - echo "AWS identity check succeeded." - else - echo "AWS CLI misconfigured; see maskfile.md for info" - exit 1 - fi - - echo "showing env diff for $proper_name from $(pwd)" - - terraform plan \ - -input=false -) -``` - -### tf apply - -> Runs the apply command interactively, still asking for confirmation, using the environment specified. - -**OPTIONS** - -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required - - - -```bash -( - cd terraform/environments/${proper_name} - - export AWS_PAGER="" - if aws sts get-caller-identity; then - echo "AWS identity check succeeded." - else - echo "AWS CLI misconfigured; see maskfile.md for info" - exit 1 - fi - - echo "applying $proper_name from $(pwd)" - - terraform apply \ - -input=false -) -``` - -### tf init - -> Runs the initialization for the environment - -**OPTIONS** - -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required - -```bash - -( - cd terraform/environments/${proper_name} - - terraform init -) -``` - -## ecs - -> commands that manage AWS containers - -### ecs deploy:all - -> Use `act` CLI to deploy all containers to a given SHA (or HEAD). - -**OPTIONS** - -- image_tag_override - - flags: -t --tag - - type: string - - desc: ECR image tag to use for this deploy (usually a Git SHA; default HEAD) -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required -- environment - - flags: -e --environment - - type: string - - desc: environment name of AWS environment (see `./aws` module) e.g. staging - - required - -```bash -( cd .. - if [[ -z $image_tag_override ]]; then - echo "Deploying HEAD ($(git rev-parse --dirty HEAD)) ... ensure this tag has been pushed!" - else - echo "Deploying override ($image_tag_override) ... ensure this tag has been pushed!" - fi - - workflow_file=".github/workflows/awsdeploy.yml" - - echo "Procedure will follow workflow $workflow_file ..." - act \ - -W "$workflow_file" \ - --container-architecture linux/amd64 \ - --input proper-name=${proper_name} \ - --input environment=${environment} \ - --input image-tag-override=${image_tag_override} \ - workflow_call - - echo "Deploy request complete! Visit AWS console to follow progress:" - echo "https://console.aws.amazon.com/ecs/v2/clusters/${proper_name}-ecs-cluster-${environment}/services" -) -``` - -### ecs deploy:one - -> Use `act` CLI to deploy ONE container/service to a given SHA (or HEAD). - -**OPTIONS** - -- image_tag_override - - flags: -t --tag - - type: string - - desc: ECR image tag to use for this deploy (usually a Git SHA; default HEAD) -- service - - flags: -s --service - - type: string - - desc: service name to update (example: core) - - required -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required -- environment - - flags: -e --environment - - type: string - - desc: environment name of AWS environment (see `./aws` module) e.g. staging - - required - -```bash -( cd .. - if [[ -z $image_tag_override ]]; then - echo "Deploying HEAD ($(git rev-parse --dirty HEAD)) ... ensure this tag has been pushed!" - else - echo "Deploying override ($image_tag_override) ... ensure this tag has been pushed!" - fi - - workflow_file=".github/workflows/deploy-template.yml" - - echo "Deploy will follow workflow $workflow_file ..." - act \ - -W "$workflow_file" \ - --container-architecture linux/amd64 \ - --input proper-name=${proper_name} \ - --input environment=${environment} \ - --input service=${service} \ - --input image-tag-override=${image_tag_override} \ - workflow_call - - echo "Deploy request complete! Visit AWS console to follow progress:" - echo "https://console.aws.amazon.com/ecs/v2/clusters/${proper_name}-ecs-cluster-${environment}/services" -) -``` - -### ecs build:all - -> Use `act` CLI to build and push all containers with local code, tagged with the HEAD (or HEAD-dirty) SHA - -No options are required -- the workflow infers them all. - -**WARN**: `docker push` invocations will appear to freeze, but that is a display bug in `act`. - -```bash - -( cd .. - workflow_file=".github/workflows/ecrbuild-all.yml" - - echo "This may take a few minutes, and output will not stream during upload ..." - echo "Procedure will follow workflow $workflow_file ..." - act \ - -W "$workflow_file" \ - --container-architecture linux/amd64 \ - workflow_call - - echo "Done!" -) -``` - -### ecs bastion - -> Opens an interactive shell on the bastion container in AWS - -**OPTIONS** - -- region - - flags: -r --region - - type: string - - desc: Which AWS region to use (default us-east-1) -- proper_name - - flags: -n --proper-name - - type: string - - desc: proper name of AWS environment (see `./aws` module); e.g. blake - - required -- environment - - flags: -e --environment - - type: string - - desc: environment name of AWS environment (see `./aws` module) e.g. staging - - required - -```bash -AWS_REGION=${region:-us-east-1} - -echo "fetching task ID of running bastion ..." -TASK=$( - aws ecs \ - list-tasks \ - --region ${AWS_REGION} \ - --cluster ${proper_name}-ecs-cluster-${environment} \ - --service ${proper_name}-bastion \ - | jq -r \ - '.taskArns[0]' \ - | cut \ - -d'/' \ - -f 3 \ -) - -echo "starting shell with task $TASK ..." -aws ecs \ - execute-command \ - --interactive \ - --command "/bin/sh" \ - --region ${AWS_REGION} \ - --container "bastion" \ - --cluster ${proper_name}-ecs-cluster-${environment} \ - --task $TASK -``` - - - -### ecs nginx:build - -> Builds the nginx container used in AWS ECS for inbound traffic - -```bash -echo "building Nginx container..." -docker build \ - --platform linux/amd64 \ - -t pubpub-v7-nginx:latest \ - ./nginx -``` - -### ecs nginx:push - -> Pushes the locally built latest nginx container - -**OPTIONS** - -- region - - flags: -r --region - - type: string - - desc: Which AWS region to use (default us-east-1) - -```bash -echo "Determining AWS Account ID..." - -AWS_REGION=${region:-us-east-1} -AWS_ACCOUNT=$( - aws sts get-caller-identity \ - --query Account \ - --output text -) -AWS_REGISTRY=$AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com - -echo "Logging docker daemon in to ECR" -aws ecr get-login-password \ - --region $AWS_REGION \ - | docker login \ - --username AWS \ - --password-stdin \ - $AWS_REGISTRY - -echo "renaming container to ECR repository..." -docker tag \ - pubpub-v7-nginx:latest \ - $AWS_REGISTRY/nginx:latest - -echo "pushing Nginx container..." -docker push \ - $AWS_REGISTRY/nginx:latest -``` diff --git a/infrastructure/nginx/Dockerfile b/infrastructure/nginx/Dockerfile deleted file mode 100644 index 493fc394d1..0000000000 --- a/infrastructure/nginx/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM nginx -COPY default.conf.template /etc/nginx/templates/default.conf.template diff --git a/infrastructure/nginx/README.md b/infrastructure/nginx/README.md deleted file mode 100644 index 5c92baab01..0000000000 --- a/infrastructure/nginx/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Nginx sidecar for pubpub v7 - -This simple container runs nginx, listening on a port (typically 8080), and forwarding -all traffic to another host (typically 127.0.0.1:). In ECS, all containers -in the same task are hosted on the same network interface and therefore have the same IP. - -The specific reason this container is needed in Pubpub-v7 is that: - -1. we have one DNS name that serves the whole application, which is backed by multiple ECS containers. -2. these containers are routed to based on path prefixes. -3. the container code, served by Next.js, is not itself aware of these path prefixes, so expect requests at `/`. -4. ALBs support path-based routing, but does not modify the requests. - -So we introduce nginx as a mediator to strip out the path prefixes. - -### Quirks - -The nginx configuration in this directory is automatically templated in by nginx because -of where it lives in `templates` directory -- you can run this container and look for the -`include` directive in the default `/etc/nginx/nginx.conf`. - -The syntax of rewrite rules is nuanced. Some subtleties, such as the slash in `/$1`, are -required for edge cases like when the path prefix is exactly `/`. - -#### /legacy_healthcheck - -The `/legacy_healthcheck` path is present to prevent ALB healthchecks, which can't be turned -off, from killing containers if we have not yet written a healthcheck endpoint. Generally, -we should implement a real, no-auth healthcheck endpoint that meaningfully indicates health -and serve it at `/healthcheck` in application code, so it is handled by the `$NGINX_PREFIX` -location block. - -### Building & change management - -This container is not built automatically since it changes so rarely. Unlike the other -services defined in this repo, the tasks are configured to refer to `latest` of this image. - -The following commands are captured in the [`maskfile.md`](../maskfile.md) infrastructure -control convenience. - -If you need to update it but don't want to set up more automation, you can run this: - -```bash -AWS_REGION=us-east-1 # set region -AWS_ACCOUNT=$( - aws sts get-caller-identity \ - --query Account \ - --output text -) -AWS_REGISTRY=$AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com - -# may need to sign in to ECR on your workstation -aws ecr get-login-password \ - --region $AWS_REGION \ - | docker login \ - --username AWS \ - --password-stdin \ - $AWS_REGISTRY - -docker build --platform linux/amd64 -t $AWS_REGISTRY/nginx:latest . # path to this directory -docker push $AWS_REGISTRY/nginx:latest -``` diff --git a/infrastructure/nginx/default.conf.template b/infrastructure/nginx/default.conf.template deleted file mode 100644 index 69bead6bec..0000000000 --- a/infrastructure/nginx/default.conf.template +++ /dev/null @@ -1,22 +0,0 @@ -upstream nextjs { - server ${NGINX_UPSTREAM_HOST}:${NGINX_UPSTREAM_PORT}; -} - -server { - listen ${NGINX_LISTEN_PORT}; - server_name _; - client_max_body_size 100m; - - location / { - proxy_pass $scheme://nextjs; - - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - } - - location /legacy_healthcheck { - return 200; - } -} diff --git a/infrastructure/terraform/.gitignore b/infrastructure/terraform/.gitignore deleted file mode 100644 index 9bad183c45..0000000000 --- a/infrastructure/terraform/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.tfstate -**/*.tfstate.* -**/.terraform -**/secrets.tfvars diff --git a/infrastructure/terraform/README.md b/infrastructure/terraform/README.md deleted file mode 100644 index c0f1bea94b..0000000000 --- a/infrastructure/terraform/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# ECS Cluster Environment - -## Dependencies - -### State file bucket/config - -You must have some way of storing terraform state files. -We use and recommend the s3 backend, but you can change -that configuration. See `./environments` for examples of -configuring backend. - -We store our terraform state in an S3 bucket created by -the `./environments/global_aws` directory, which has an -interactive setup, see that readme for more info. - -## Change management and workflows - -This code is here to make infrastructure declarative rather than imperative. -It secondarily includes modularization to make it hard for configuration to drift -between preproduction / production or open source deployments. -These are two separate concerns. - -Declarative code changes are still managed imperatively with `terraform apply`, -which can be made partially or fully automatic. - -In general, production changes are applied manually after we are satisfied with -preproduction, which may or may not be automatic. Developers should expect a flow like: - -1. make a change to a shared module code -2. make matching change to configuration in ALL environment directories, so they can be reviewed together -3. apply this new SHA to staging and do validation as desired -4. apply this same SHA to production. - -Now there is no drift between code, staging, and production - we are converged. - -## Rollbacks - -Generally, rollbacks are done in emergencies and are done first in prod. (If done first in staging, this is -really no different a process than a roll-forward). Rollbacks are the only situation in which we should expect -to deploy production from an off-main branch. Changes may be infrastructure or code. In the infrastructure workflow: - -1. make changes to terraform code that seems to fix the issue and apply it to production -2. if it resolves the issue, figure out how it needs to be applied to pre-prod for consistency and open a PR -3. when this PR is merged, it deploys to pre-prod and we are converged. - -In general, code rollbacks can be done without a re-build, by deploying an old SHA, but it is preferable -if there is time, to do a revert & roll-forward flow, -because some operations (primarily database migrations) operate on assumptions of monotonic time. Additionally -this flow makes it easier for rollbacks to include reverts of specific changes in the middle of the commit history -without reverting everything more recent. - -## Adding/updating variables and configuration - -Variables are the things that distinguish one environment from another. These include container variables and -certain extra values such as infrastructure scaling / footprint parameters. There is a tradeoff between ease -of configuration change and strength of guarantees given by similarity between staging and prod. First decide if -your change should be applied identically to each environment, or warrants an increase in drift. - -To add a variable, modify some terraform resource that depends on it and then thread your way back up. The most -common case will be to add an environment variable to a container so will use that as example here: - -1. modify `modules/deployment/main.tf` to add a variable to the appropriate invocation of `container-generic`. -1. modify `modules/deployment/variables.tf` to add the variable declaration. (This step is not needed if your new env var can be computed based on changes to the upstream infrastructure, such as a database URL.) -1. modify each invocation in `environments/*/main.tf` to add this new variable. - -Proceed as above. Note that changes to task definitions (which include container configs) are not actually applied until you then trigger a new `deploy` using `act`/`mask` or the Github console. - -## Adding secrets - -Secrets are a special variety of environment variable, whose process is just like the above but with an extra step after `terraform apply` and before `mask ecs deploy`: - -To provide secrets to ECS containers, you should put them in AWS Secrets Manager. -To do this, replicate the setup in `modules/core-services/main.tf`: create a resource -that declares the existence of the secret. Since the purpose of this model is to -avoid having copies of the secrets exist anywhere persistently except the single -locked-down place, naturally the secret value itself (the "version") can't be passed through terraform. - -So you must one-time only, or when changing the secret, - -1. go to the AWS Secrets Manager console -2. and choose your new secret -3. select "Retrieve secret value" (unintuitive, because there is no value yet) -4. Console says "Value does not yet exist" and button you just clicked becomes "Set secret value". -5. Probably, paste your secret in the "plain text" box (you can also do key-value pairs, but then must use the key in the address when retrieving.) diff --git a/infrastructure/terraform/environments/blake/.terraform.lock.hcl b/infrastructure/terraform/environments/blake/.terraform.lock.hcl deleted file mode 100644 index e390b3c19d..0000000000 --- a/infrastructure/terraform/environments/blake/.terraform.lock.hcl +++ /dev/null @@ -1,47 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.70.0" - constraints = ">= 5.0.0" - hashes = [ - "h1:kcKscQCmMLrNMAkaL4XIqGGq4uk8vXthNRvtfersNH0=", - "zh:09cbec93c324e6f03a866244ecb2bae71fdf1f5d3d981e858b745c90606b6b6d", - "zh:19685d9f4c9ddcfa476a9a428c6c612be4a1b4e8e1198fbcbb76436b735284ee", - "zh:3358ee6a2b24c982b7c83fac0af6898644d1bbdabf9c4e0589e91e427641ba88", - "zh:34f9f2936de7384f8ed887abdbcb54aea1ce7b0cf2e85243a3fd3904d024747f", - "zh:4a99546cc2140304c90d9ccb9db01589d4145863605a0fcd90027a643ea3ec5d", - "zh:4da32fec0e10dab5aa3dea3c9fe57adc973cc73a71f5d59da3f65d85d925dc3f", - "zh:659cf94522bc38ce0af70f7b0371b2941a0e0bcad02d17c1a7b264575fe07224", - "zh:6f1c172c9b98bc86e4f0526872098ee3246c2620f7b323ce0c2ce6427987f7d2", - "zh:79bf8fb8f37c308742e287694a9de081ff8502b065a390d1bcfbd241b4eca203", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:b7a5e1dfd9e179d70a169ddd4db44b56da90309060e27d36b329fe5fb3528e29", - "zh:c2cc728cb18ffd5c4814a10c203452c71f5ab0c46d68f9aa9183183fa60afd87", - "zh:c89bb37d2b8947c9a0d62b0b86ace51542f3327970f4e56a68bf81d9d0b8b65b", - "zh:ef2a61e8112c3b5e70095508aadaadf077e904b62b9cfc22030337f773bba041", - "zh:f714550b858d141ea88579f25247bda2a5ba461337975e77daceaf0bb7a9c358", - ] -} - -provider "registry.terraform.io/honeycombio/honeycombio" { - version = "0.27.1" - constraints = ">= 0.22.0" - hashes = [ - "h1:2T/JgeekYNwS4oXjdzduRZBcCR9lpuU/1Wo/kG4YcUA=", - "zh:09be7a66f926202a976cce5eb5fd4770aaa8202c5f4cd3611fb34288c2a3d0f5", - "zh:128a522a0a991674b80dbc66e3859b56fd1590ecf63a2cf6323d04d8212175b9", - "zh:18869e171aeacb7db79ba06d284ce017e4bd5afaf3baa0171a66c279cf6a2b7f", - "zh:228aa0524c4a66ef6ebcca4103aee769d3d338fc01120180d01969c88bdaaf2b", - "zh:3dfea9f01cf9fb9853573b131229eb0894831e90807e57d86538afb8648486c1", - "zh:432e8d712b73c9cc03a55e7495f92ba302c71655ba38e22d1dfe32db8526384a", - "zh:58f737a09add7ef9f84abbc531c4ca92ba96fdd2cafa063214ff1cb5a48158d2", - "zh:6b9b7042a8e16d7b4dcc9ad0e6ca1d52af419da0422036da70a60520c1323018", - "zh:93d73245f15ac5ea3fe815e1b267f8a6ed4938c9aa450db18e906c44da743e5c", - "zh:95da88c87605ac65eceedc1c0975d42bd56c3552da9d6ec60b60889d1378ebd7", - "zh:9c26856ba26a99bc3ca29ec33e18737f0cdf74fcf7580bbdee573971710204bb", - "zh:aa1c9831880e7c1fb1285bf9ef5c3c89ec977ce9342fe5bc79df4f641ca475d6", - "zh:bbb1d5368c0e575afe870838aa6358fda13555ceebe5239f8a5fd6b305f5a1fd", - "zh:c443152c32660d80da960257166b3fd8bb6d0c0154b4e16d01d2360e6588c9b2", - ] -} diff --git a/infrastructure/terraform/environments/blake/main.tf b/infrastructure/terraform/environments/blake/main.tf deleted file mode 100644 index a67474ab12..0000000000 --- a/infrastructure/terraform/environments/blake/main.tf +++ /dev/null @@ -1,65 +0,0 @@ -###### -## -## Terraform-meta configurations -## -###### - -terraform { - required_version = ">= 1.5.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.0" - } - - honeycombio = { - source = "honeycombio/honeycombio" - version = ">= 0.22.0" - } - } - backend "s3" { - bucket = "pubpub-tfstates" - key = "ecs-blake.tfstate" - region = "us-east-1" - } -} - -provider "aws" { - region = local.region -} - -###### -## -## Environment-specific configuration -## -###### - -locals { - name = "blake" - environment = "staging" - region = "us-east-1" - - pubpub_hostname = "blake.duqduq.org" - site_builder_hostname = "bob.duqduq.org" # get it, like the builder - - route53_zone_id = "Z059164612717GL8VGM95" - ecr_repository_urls = { - core = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-core" - jobs = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-jobs" - nginx = "246372085946.dkr.ecr.us-east-1.amazonaws.com/nginx" - root = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7" - site_builder = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-site-builder" - } - - MAILGUN_SMTP_USERNAME = "v7@mg.pubpub.org" - ASSETS_BUCKET_NAME = "assets.blake.pubpub.org" - HOSTNAME = "0.0.0.0" - DATACITE_API_URL = "https://api.test.datacite.org" -} - - -###### -## -## Complete generic environment -## -###### diff --git a/infrastructure/terraform/environments/cloudflare/.terraform.lock.hcl b/infrastructure/terraform/environments/cloudflare/.terraform.lock.hcl deleted file mode 100644 index 35b9288d6d..0000000000 --- a/infrastructure/terraform/environments/cloudflare/.terraform.lock.hcl +++ /dev/null @@ -1,48 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/cloudflare/cloudflare" { - version = "4.30.0" - constraints = "~> 4.0" - hashes = [ - "h1:FhhTF09/BBk37akGLFx9/uWkGUGwSNRub8vP80TaF7Q=", - "zh:218d1948b59e3d2e3af082724a0d057bcca5a5643c5e7c3b85eefc02430edd6b", - "zh:24eb677bc1b205565efb5c0d1c464f63d1e240aac61f5b2ef15165fe842cb7e2", - "zh:27896ed2a4f05f6a46ef25e674e445e89bd4bfba8cddbe95940109c6dc3179cc", - "zh:38b3b8297a9650b0ed09d57e0d802f5d851062bdadf72825652232c9a67346ac", - "zh:58d49ec9f414d0ff71e94cc991e1e3e33a13502ce0fea1393edd1297d0877bab", - "zh:5ed92c556e72cc4ea7fdf6db9e0dd7b093d179e26f2d2989b21a004a6402f2ae", - "zh:71f5c64702a7b2102f6d5edfd767953cd5b1248093c05983b909de06cf0c40cc", - "zh:788a023967db63b8eda9c0415851a743daf4073bab66b0bd1204bccbb54c9f8f", - "zh:7b9cd30355b4f63941284998167c3f3e5d208685e5176928275436de012f62d2", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:923ec04258fde407f0fce80488268f4277ffac68fb7240eee4f4373a344c5469", - "zh:97473bdb848a7f77832fde6d0e68877bdcc17bf47ae3639fb09e1aeff4a92a01", - "zh:9b8754d8f7c15878ecb8897a6ffc4e9ec95f4e5f0560f4129af82a8200e602ea", - "zh:b890723ed524d34e7fbee6c119714be23e1783b82441ce4c18871c9d54f10cbd", - "zh:c75e0e5f406653c9b4928d97a38410ad7bb20d48e260c17ae3125a77b0457bf5", - ] -} - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.33.0" - constraints = ">= 2.0.0" - hashes = [ - "h1:kPm7PkwHh6tZ74pUj5C/QRPtauxdnzrEG2yhCJla/4o=", - "zh:10bb683f2a9306e881f51a971ad3b2bb654ac94b54945dd63769876a343b5b04", - "zh:3916406db958d5487ea0c2d2320012d1907c29e6d01bf693560fe05e38ee0601", - "zh:3cb54b76b2f9e30620f3281ab7fb20633b1e4584fc84cc4ecd5752546252e86f", - "zh:513bcfd6971482215c5d64725189f875cbcbd260c6d11f0da4d66321efd93a92", - "zh:545a34427ebe7a950056627e7c980c9ba16318bf086d300eb808ffc41c52b7a8", - "zh:5a44b90faf1c8e8269f389c04bfac25ad4766d26360e7f7ac371be12a442981c", - "zh:64e1ef83162f78538dccad8b035577738851395ba774d6919cb21eb465a21e3a", - "zh:7315c70cb6b7f975471ea6129474639a08c58c071afc95a36cfaa41a13ae7fb9", - "zh:9806faae58938d638b757f54414400be998dddb45edfd4a29c85e827111dc93d", - "zh:997fa2e2db242354d9f772fba7eb17bd6d18d28480291dd93f85a18ca0a67ac2", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f9e076b7e9752971f39eead6eda69df1c5e890c82ba2ca95f56974af7adfe79", - "zh:b1d6af047f96de7f97d38b685654f1aed4356d5060b0e696d87d0270f5d49f75", - "zh:bfb0654b6f34398aeffdf907b744af06733d168db610a2c5747263380f817ac7", - "zh:e25203ee8cedccf60bf450950d533d3c172509bda8af97dbc3bc817d2a503c57", - ] -} diff --git a/infrastructure/terraform/environments/cloudflare/README.md b/infrastructure/terraform/environments/cloudflare/README.md deleted file mode 100644 index 08f6b97774..0000000000 --- a/infrastructure/terraform/environments/cloudflare/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Global Cloudflare configuration - -This module should generally be created by an admin, -and assumees the following permissions which are sensitive: - -**Cloudflare read-write token** set at `CLOUDFLARE_API_TOKEN`. In general, this -secret can be used for very nefarious things and should be extra sensitively protected. - -**AWS read-write permissions**: in `~/.aws/credentials`. see `../maskfile.md` for more info. - -## Relationship to AWS environments - -AWS environments assume existence of the Route53 zone and DNS NS records that refer authority -to that zone. If you are not using Cloudflare this module is not needed for those environments, -but in general to create a new env it is expected to augment this module with NS records referring -to this route53 configuration for domains subordinate to that new AWS env. - -Therefore updates to this module, which should happen very infrequently, should be applied before -you attempt to create the new AWS-ECS environment, otherwise that will fail due to the AWS Certificate -Manager being unsuccessful in validating your ownership of the DNS. diff --git a/infrastructure/terraform/environments/cloudflare/main.tf b/infrastructure/terraform/environments/cloudflare/main.tf deleted file mode 100644 index fbf961f720..0000000000 --- a/infrastructure/terraform/environments/cloudflare/main.tf +++ /dev/null @@ -1,64 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 1.5.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 6.0" - } - - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 4.0" - } - } - backend "s3" { - bucket = "pubpub-tfstates" - key = "cloudflare.tfstate" - region = "us-east-1" - } -} - - -provider "aws" { - region = "us-east-1" -} - -###### -# -## Configuration of routing from Cloudflare to Route53. -# -###### - -locals { - duqduq_domain = "duqduq.org" - pubpub_domain = "pubpub.org" -} - -data "cloudflare_zone" "duqduq" { - name = local.duqduq_domain -} - -resource "aws_route53_zone" "duqduq" { - name = local.duqduq_domain -} - -# do this for all subdomains of duqduq that need to be NS'd to v7 -data "cloudflare_zone" "pubpub" { - name = local.pubpub_domain -} - -resource "aws_route53_zone" "pubpub" { - name = local.pubpub_domain -} -resource "cloudflare_record" "ns_pubpub" { - for_each = toset(["0", "1", "2", "3"]) - type = "NS" - - zone_id = data.cloudflare_zone.pubpub.id - - name = "app.${local.pubpub_domain}" - - value = aws_route53_zone.pubpub.name_servers[tonumber(each.key)] -} diff --git a/infrastructure/terraform/environments/cloudflare/outputs.tf b/infrastructure/terraform/environments/cloudflare/outputs.tf deleted file mode 100644 index df4a0eaa4f..0000000000 --- a/infrastructure/terraform/environments/cloudflare/outputs.tf +++ /dev/null @@ -1,6 +0,0 @@ -output "route53_zones" { - value = { - "pubpub.org" = aws_route53_zone.pubpub.zone_id - "duqduq.org" = aws_route53_zone.duqduq.zone_id - } -} diff --git a/infrastructure/terraform/environments/global_aws/.terraform.lock.hcl b/infrastructure/terraform/environments/global_aws/.terraform.lock.hcl deleted file mode 100644 index 35b9288d6d..0000000000 --- a/infrastructure/terraform/environments/global_aws/.terraform.lock.hcl +++ /dev/null @@ -1,48 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/cloudflare/cloudflare" { - version = "4.30.0" - constraints = "~> 4.0" - hashes = [ - "h1:FhhTF09/BBk37akGLFx9/uWkGUGwSNRub8vP80TaF7Q=", - "zh:218d1948b59e3d2e3af082724a0d057bcca5a5643c5e7c3b85eefc02430edd6b", - "zh:24eb677bc1b205565efb5c0d1c464f63d1e240aac61f5b2ef15165fe842cb7e2", - "zh:27896ed2a4f05f6a46ef25e674e445e89bd4bfba8cddbe95940109c6dc3179cc", - "zh:38b3b8297a9650b0ed09d57e0d802f5d851062bdadf72825652232c9a67346ac", - "zh:58d49ec9f414d0ff71e94cc991e1e3e33a13502ce0fea1393edd1297d0877bab", - "zh:5ed92c556e72cc4ea7fdf6db9e0dd7b093d179e26f2d2989b21a004a6402f2ae", - "zh:71f5c64702a7b2102f6d5edfd767953cd5b1248093c05983b909de06cf0c40cc", - "zh:788a023967db63b8eda9c0415851a743daf4073bab66b0bd1204bccbb54c9f8f", - "zh:7b9cd30355b4f63941284998167c3f3e5d208685e5176928275436de012f62d2", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:923ec04258fde407f0fce80488268f4277ffac68fb7240eee4f4373a344c5469", - "zh:97473bdb848a7f77832fde6d0e68877bdcc17bf47ae3639fb09e1aeff4a92a01", - "zh:9b8754d8f7c15878ecb8897a6ffc4e9ec95f4e5f0560f4129af82a8200e602ea", - "zh:b890723ed524d34e7fbee6c119714be23e1783b82441ce4c18871c9d54f10cbd", - "zh:c75e0e5f406653c9b4928d97a38410ad7bb20d48e260c17ae3125a77b0457bf5", - ] -} - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.33.0" - constraints = ">= 2.0.0" - hashes = [ - "h1:kPm7PkwHh6tZ74pUj5C/QRPtauxdnzrEG2yhCJla/4o=", - "zh:10bb683f2a9306e881f51a971ad3b2bb654ac94b54945dd63769876a343b5b04", - "zh:3916406db958d5487ea0c2d2320012d1907c29e6d01bf693560fe05e38ee0601", - "zh:3cb54b76b2f9e30620f3281ab7fb20633b1e4584fc84cc4ecd5752546252e86f", - "zh:513bcfd6971482215c5d64725189f875cbcbd260c6d11f0da4d66321efd93a92", - "zh:545a34427ebe7a950056627e7c980c9ba16318bf086d300eb808ffc41c52b7a8", - "zh:5a44b90faf1c8e8269f389c04bfac25ad4766d26360e7f7ac371be12a442981c", - "zh:64e1ef83162f78538dccad8b035577738851395ba774d6919cb21eb465a21e3a", - "zh:7315c70cb6b7f975471ea6129474639a08c58c071afc95a36cfaa41a13ae7fb9", - "zh:9806faae58938d638b757f54414400be998dddb45edfd4a29c85e827111dc93d", - "zh:997fa2e2db242354d9f772fba7eb17bd6d18d28480291dd93f85a18ca0a67ac2", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f9e076b7e9752971f39eead6eda69df1c5e890c82ba2ca95f56974af7adfe79", - "zh:b1d6af047f96de7f97d38b685654f1aed4356d5060b0e696d87d0270f5d49f75", - "zh:bfb0654b6f34398aeffdf907b744af06733d168db610a2c5747263380f817ac7", - "zh:e25203ee8cedccf60bf450950d533d3c172509bda8af97dbc3bc817d2a503c57", - ] -} diff --git a/infrastructure/terraform/environments/global_aws/README.md b/infrastructure/terraform/environments/global_aws/README.md deleted file mode 100644 index 7b15bfdb87..0000000000 --- a/infrastructure/terraform/environments/global_aws/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Global configurations - -This module should generally be created by an admin, -and not applied or updated by a machine user. - -## Creation of the terraform state bucket - -1. Uncomment the code creating this bucket; comment the backend block -1. terraform init -1. Set the bucket name -1. terraform apply -1. `terraform state rm aws_s3_bucket.terraform_state` -1. comment the bucket definition; uncomment the backend block -1. terraform init ("yes" to copying the state file) -1. destroy local copies of the state file - -This bucket name can now be in your s3.tfbackend files everywhere. diff --git a/infrastructure/terraform/environments/global_aws/github_actions_iam.tf b/infrastructure/terraform/environments/global_aws/github_actions_iam.tf deleted file mode 100644 index 8391ee9cb9..0000000000 --- a/infrastructure/terraform/environments/global_aws/github_actions_iam.tf +++ /dev/null @@ -1,159 +0,0 @@ -# iam user for Github Actions - -resource "aws_iam_user" "github_actions" { - name = "github_actions" - path = "/" -} - -resource "aws_iam_access_key" "github_actions" { - user = aws_iam_user.github_actions.name -} - -resource "aws_iam_policy" "lightsail" { - name = "lightsail" - policy = jsonencode({ - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Action: [ - "lightsail:*" - ], - Resource: "*" - } - ] - }) -} - -resource "aws_iam_policy" "ecr" { - name = "ECRAdmin" - policy = jsonencode({ - Version : "2012-10-17", - Statement : [ - { - Sid : "EcrAdmin", - Effect : "Allow", - Action : [ - "ecr:*" - ], - Resource : [ - "*" - ] - } - ] - }) -} - -// iam policy to allow aws ecs update-service -resource "aws_iam_policy" "ecs" { - name = "ECSUpdateService" - policy = jsonencode({ - Version : "2012-10-17", - Statement : [ - { - Sid : "AllowPassRole", - Effect : "Allow", - Action : [ - "iam:PassRole" - ], - Resource : [ - "*" - ] - }, - { - Sid : "EcsUpdateService", - Effect : "Allow", - Action : [ - "ecs:UpdateService", - "ecs:DescribeServices", - "ecs:DescribeClusters", - "ecs:DescribeTaskDefinition", - "ecs:CreateTaskSet", - "ecs:RegisterTaskDefinition", - "ecs:DeleteTaskDefinitions", - "ecs:DeleteService", - "ecs:UpdateServicePrimaryTaskSet", - "ecs:StopTask", - "ecs:StartTask", - "ecs:RunTask", - "ecs:CreateService", - "ecs:DescribeTasks", - "ecs:ListServices", - "ecs:ListTaskDefinitions", - ], - Resource : [ - "*" - ] - } - ] - }) -} - -// read access to all the secrets that github actions needs -resource "aws_iam_policy" "github_actions_secrets" { - name = "GithubActionsSecrets" - policy = jsonencode({ - Version : "2012-10-17", - Statement : [ - { - Effect : "Allow", - Action : [ - "secretsmanager:GetSecretValue" - ], - Resource : [ - # necessary to set during build in order to upload source maps to sentry - "arn:aws:secretsmanager:*:*:secret:sentry-auth-token-*" - ] - } - ] - }) -} - -resource "aws_iam_role" "github_actions_role" { - name = "github_actions_role" - - # Terraform's "jsonencode" function converts a - # Terraform expression result to valid JSON syntax. - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = [ - "sts:AssumeRole", - "sts:TagSession", // specifically used by AWS provided GH action modules - ] - Effect = "Allow" - Principal = { - AWS = [aws_iam_user.github_actions.arn] - } - }, - ] - }) -} - -resource "aws_iam_role_policy_attachment" "gha_attach_ecr" { - role = aws_iam_role.github_actions_role.name - policy_arn = aws_iam_policy.ecr.arn -} - -resource "aws_iam_role_policy_attachment" "gha_attach_ecs" { - role = aws_iam_role.github_actions_role.name - policy_arn = aws_iam_policy.ecs.arn -} - -resource "aws_iam_role_policy_attachment" "gha_attach_secrets" { - role = aws_iam_role.github_actions_role.name - policy_arn = aws_iam_policy.github_actions_secrets.arn -} - -// TODO: create a new user for pullpreview and remove both user policy attachments below - -resource "aws_iam_user_policy_attachment" "gha_user_attach_lightsail" { - user = aws_iam_user.github_actions.name - policy_arn = aws_iam_policy.lightsail.arn -} - -resource "aws_iam_user_policy_attachment" "gha_user_attach_ecr" { - user = aws_iam_user.github_actions.name - policy_arn = aws_iam_policy.ecr.arn -} diff --git a/infrastructure/terraform/environments/global_aws/main.tf b/infrastructure/terraform/environments/global_aws/main.tf deleted file mode 100644 index a9546b8c9c..0000000000 --- a/infrastructure/terraform/environments/global_aws/main.tf +++ /dev/null @@ -1,47 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 1.5.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 2.0" - } - - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 4.0" - } - } - backend "s3" { - bucket = "pubpub-tfstates" - key = "global.tfstate" - region = "us-east-1" - } -} - - -provider "aws" { - region = "us-east-1" -} - -## s3 bucket for terraform state -# -# This resource was created using terraform with these fields. -# However, it is dangerous to manage terraform state files in -# a bucket that is itself Terraform-configured. -# -# it has been removed with `terraform state rm`, but this -# declaration is left here for posterity. -# -# resource "aws_s3_bucket" "terraform_state" { -# bucket = "pubpub-tfstates" -# acl = "private" -# versioning { -# enabled = true -# } -# } - -module "ecr_repositories" { - source = "../../modules/ecr-repositories" -} diff --git a/infrastructure/terraform/environments/global_aws/outputs.tf b/infrastructure/terraform/environments/global_aws/outputs.tf deleted file mode 100644 index 96a23d8ee1..0000000000 --- a/infrastructure/terraform/environments/global_aws/outputs.tf +++ /dev/null @@ -1,20 +0,0 @@ -# if resources are needed in an environment's inputs, -# then you can use terraform_remote_state to get this module's output - -# put these creds in github actions secrets config -output "github_actions_user_credential" { - value = { - id = aws_iam_access_key.github_actions.id - secret = aws_iam_access_key.github_actions.secret - } - - # prevents this secret value from appearing accidentally - # NOTE - it is still saved in the state file. - sensitive = true -} - -# Provide this value to github actions -output "github_actions_role_to_assume_arn" { - value = aws_iam_role.github_actions_role.arn -} - diff --git a/infrastructure/terraform/environments/stevie/.terraform.lock.hcl b/infrastructure/terraform/environments/stevie/.terraform.lock.hcl deleted file mode 100644 index 9e5f20d1e7..0000000000 --- a/infrastructure/terraform/environments/stevie/.terraform.lock.hcl +++ /dev/null @@ -1,66 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.100.0" - constraints = ">= 4.0.0, >= 4.40.0, >= 4.66.1, >= 5.0.0, ~> 5.0, >= 5.83.0" - hashes = [ - "h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=", - "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", - "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", - "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", - "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", - "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", - "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", - "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", - "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", - "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", - "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", - "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", - "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", - "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.7.2" - hashes = [ - "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", - "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", - "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", - "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", - "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", - "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", - "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", - "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", - "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", - "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", - "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", - ] -} - -provider "registry.terraform.io/honeycombio/honeycombio" { - version = "0.40.1" - constraints = ">= 0.22.0" - hashes = [ - "h1:MTxI2Ut3RVM3Tck1fUCl4cIAG2eLJx1Ic7fqUhAEj2E=", - "zh:0df5bcfb2169344f1049d8af6043ed43ec39c430d3dd09eec58351024d42de26", - "zh:12bf35501a5da78faab3385b370061c4100b7c4c38ca90423113d6707a713cc8", - "zh:13971b47ff96cb679edef089f94df4dc3223173b385683bd76e38e4f21ae293f", - "zh:25b1468ac30d15afb15c10877bc335b81ac5fd0a4e45857f96f7cf104e7e4e08", - "zh:31aadf8db4c7f60aa71e0fbb739d1c379006066f5cde00e22df464ad7ab527e8", - "zh:3c0fa9918c7996490409213df693af791d4b6f919dea55697c719d45f25b4bbc", - "zh:50e08fa7d728e9fa5a37d8c6e0f9b0067803aba810b74111be03ef7060df47d8", - "zh:53ab15db29a2833d1d5b3eec4e93c5635d9c1504efa6a4c61000488d3c64995b", - "zh:6d7983ab0be0dcedbd5a7d21b7a66f23ead32a00c3492ca7474c2d172b35f6dd", - "zh:7adb2327505e5ddaaaeb927e6de23a91a763efc67e4db5556d12e33d77348599", - "zh:add36efbe9c182149f932a77ccaa8cec46edb2ce9095aa3b735ceba8bfd07738", - "zh:c3642ea008f2bfe2fb362e941630c34791dc6387b57ffec86fd1061d2eb2f1a6", - "zh:e3c74be8effb30f740d7795ac0fe2194e410bc3274ebbc81653c8d14fbd0b0c2", - "zh:e447c171225f79d5cdf284d9112c5df4d77d7fec038a251757dc69379e82f943", - ] -} diff --git a/infrastructure/terraform/environments/stevie/main.tf b/infrastructure/terraform/environments/stevie/main.tf deleted file mode 100644 index 109e762be7..0000000000 --- a/infrastructure/terraform/environments/stevie/main.tf +++ /dev/null @@ -1,83 +0,0 @@ -###### -## -## Terraform-meta configurations -## -###### - -terraform { - required_version = ">= 1.5.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.0" - } - - honeycombio = { - source = "honeycombio/honeycombio" - version = ">= 0.22.0" - } - } - backend "s3" { - bucket = "pubpub-tfstates" - key = "ecs-stevie-PROD.tfstate" - region = "us-east-1" - } -} - -provider "aws" { - region = local.region -} - -###### -## -## Environment-specific configuration -## -###### - -locals { - name = "stevie" - environment = "production" - region = "us-east-1" - - pubpub_hostname = "app.pubpub.org" - site_builder_hostname = "bob.pubpub.org" # get it, like the builder - - route53_zone_id = "Z00255803PJ09HVWNKPVY" - ecr_repository_urls = { - core = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-core" - jobs = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-jobs" - nginx = "246372085946.dkr.ecr.us-east-1.amazonaws.com/nginx" - root = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7" - site_builder = "246372085946.dkr.ecr.us-east-1.amazonaws.com/pubpub-v7-site-builder" - } - - MAILGUN_SMTP_USERNAME = "v7@mg.pubpub.org" - ASSETS_BUCKET_NAME = "assets.app.pubpub.org" - HOSTNAME = "0.0.0.0" - DATACITE_API_URL = "https://api.datacite.org" -} - - -###### -## -## Complete generic environment -## -###### - -module "deployment" { - source = "../../modules/deployment" - - name = local.name - environment = local.environment - region = local.region - - pubpub_hostname = local.pubpub_hostname - site_builder_hostname = local.site_builder_hostname - route53_zone_id = local.route53_zone_id - ecr_repository_urls = local.ecr_repository_urls - - MAILGUN_SMTP_USERNAME = local.MAILGUN_SMTP_USERNAME - ASSETS_BUCKET_NAME = local.ASSETS_BUCKET_NAME - HOSTNAME = local.HOSTNAME - DATACITE_API_URL = local.DATACITE_API_URL -} diff --git a/infrastructure/terraform/modules/container-generic/README.md b/infrastructure/terraform/modules/container-generic/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/terraform/modules/container-generic/main.tf b/infrastructure/terraform/modules/container-generic/main.tf deleted file mode 100644 index 11b46c1bff..0000000000 --- a/infrastructure/terraform/modules/container-generic/main.tf +++ /dev/null @@ -1,193 +0,0 @@ -locals { - # null-guard here is annoying but necessary - public = var.listener != null ? var.listener.public : false - - # a shortname for placaes where name lengths are constrained - shortname = substr(sha1(var.service_name), 0, 4) - - # a block to DRY out - log_configuration = { - logDriver = "awslogs", - options = { - awslogs-group = var.cluster_info.cloudwatch_log_group_name, - awslogs-region = var.cluster_info.region, - awslogs-stream-prefix = "ecs" - } - } -} - -module "ecs_service" { - source = "terraform-aws-modules/ecs/aws//modules/service" - version = "~> 5.0" - - name = "${var.cluster_info.name}-${var.service_name}" - - cluster_arn = var.cluster_info.cluster_arn - enable_execute_command = true - - # allow github actions to update the service without confusing TF - ignore_task_definition_changes = false - - cpu = var.resources.cpu - memory = var.resources.memory - desired_count = var.resources.desired_count - # execution_role_arn = aws_iam_role.ecs_task_execution_role.arn - # task_role_arn = aws_iam_role.ecs_task_role.arn - - - - # TEMPLATE Container definition(s). - container_definitions = merge({ - - "${var.service_name}" = { - essential = true - image = "${var.repository_url}:latest" - - # don't open ports unless inbound network is configured - port_mappings = var.listener != null ? [{ - name = var.listener.service_name - protocol = var.listener.protocol - hostPort = var.listener.from_port - containerPort = var.listener.to_port - }] : [] - - restartPolicy = { - enabled = true - restartAttemptPeriod = 60 - } - - # use concat() to add a computible variable - environment = concat( - var.configuration.environment, - [{ name = "OTEL_SERVICE_NAME", value = "${var.service_name}.${var.service_name}" }], - ) - secrets = var.configuration.secrets - - readonly_root_filesystem = false - - # wait for the init containers to finish - # (this behavior is true for migrations, might need to be more - # configurable if we have other init containers later) - dependencies = [for ic in var.init_containers : { - containerName = ic.name - condition = "SUCCESS" - }] - - log_configuration = local.log_configuration - - command = var.command - } - }, - { - for ic in var.init_containers : ic.name => { - essential = false - image = ic.image - command = ic.command - - environment = concat( - var.configuration.environment, - [{ name = "OTEL_SERVICE_NAME", value = "${var.service_name}.${ic.name}" }], - ) - secrets = var.configuration.secrets - - readonly_root_filesystem = false - - log_configuration = local.log_configuration - } - }, - local.public ? { # optional Nginx container - nginx = { - essential = true - image = var.nginx_image - port_mappings = var.listener != null ? [{ - name = "${var.service_name}-nginx" - protocol = "tcp" - hostPort = 8080 - containerPort = 8080 - }] : [] - - environment = [ - { name = "OTEL_SERVICE_NAME", - value = "${var.service_name}.nginx" }, - { name = "NGINX_LISTEN_PORT", - value = "8080" }, - { name = "NGINX_PREFIX", - value = var.listener.path_prefix }, - { name = "NGINX_UPSTREAM_HOST", - # Containers in the same Task share one network interface: - # https://aws.amazon.com/blogs/compute/task-networking-in-aws-fargate/ - value = "127.0.0.1" }, - { name = "NGINX_UPSTREAM_PORT", - value = var.listener.from_port }, - ] - - readonly_root_filesystem = false - - log_configuration = local.log_configuration - } - } : {}) - - load_balancer = local.public ? { - service = { - target_group_arn = aws_lb_target_group.this[0].arn - # note that this is may not match the listener's service name - container_name = "nginx" - container_port = 8080 - } - } : {} - - - subnet_ids = var.cluster_info.private_subnet_ids - security_group_ids = var.cluster_info.container_security_group_ids - # TODO: set this to true to make the outbound traffic non-NAT, aka cheaper but unstable. - assign_public_ip = false - - tags = { - Environment = "${var.cluster_info.name}-${var.cluster_info.environment}" - Project = "Pubpub-v7" - LogicalName = var.service_name - Shortname = local.shortname - ShortnameAnnotation = "Shortname is calculated as first four characters of the sha1sum of the Logical Name." - } -} - -resource "aws_lb_target_group" "this" { - count = local.public ? 1 : 0 - # use shortname here because this string is max 32 chars - name = "${var.cluster_info.name}-${local.shortname}" - port = 80 - protocol = "HTTP" - vpc_id = var.cluster_info.vpc_id - target_type = "ip" - - # this healthcheck is specified on the nginx container - # amd always passes, so is only useful as a fallback - # when the container does not provide a more meaningful - # one. - health_check { - path = coalesce(var.health_check_path, "/legacy_healthcheck") - interval = "5" - protocol = "HTTP" - matcher = "200" - timeout = "2" - unhealthy_threshold = "3" - healthy_threshold = "3" - } -} - -resource "aws_lb_listener_rule" "http" { - count = local.public ? 1 : 0 - listener_arn = var.cluster_info.lb_listener_arn - priority = var.listener.rule_priority - - action { - type = "forward" - target_group_arn = aws_lb_target_group.this[count.index].arn - } - - condition { - path_pattern { - values = ["${var.listener.path_prefix}*"] - } - } -} diff --git a/infrastructure/terraform/modules/container-generic/outputs.tf b/infrastructure/terraform/modules/container-generic/outputs.tf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/terraform/modules/container-generic/variables.tf b/infrastructure/terraform/modules/container-generic/variables.tf deleted file mode 100644 index 5f2ed802be..0000000000 --- a/infrastructure/terraform/modules/container-generic/variables.tf +++ /dev/null @@ -1,107 +0,0 @@ -variable "cluster_info" { - description = "infrastructure values output from v7-cluster" - - type = object({ - region = string - name = string - vpc_id = string - cluster_arn = string - environment = string - private_subnet_ids = list(string) - container_security_group_ids = list(string) - cloudwatch_log_group_name = string - lb_listener_arn = string - # service_namespace_arn = string - }) -} - -variable "service_name" { - description = "name for this service" -} - -variable "repository_url" { - description = "url to the image repository (excluding tag)" - type = string -} -variable "nginx_image" { - description = "fully qualified nginx image to pull (including tag)" - type = string - default = null -} - -variable "resources" { - description = "resources available to this container service" - type = object({ - cpu = number - memory = number - desired_count = number - }) - - default = { - cpu = 512 - memory = 1024 - desired_count = 1 - } -} - -variable "init_containers" { - description = "list of init container specs to run before starting" - type = list(object({ - name = string - image = string - command = list(string) - })) - default = [] -} - -variable "listener" { - description = "specification of the inbound network addressibility" - default = null - - type = object({ - service_name = string - # whether to expose this to inbound internet traffic - public = bool - # the path prefix for public routes from the ALB hostname - # - MUST end in a slash. - path_prefix = string - # priority, in case this conflicts with other rules. - # lower numbers are evaluated first, so more specific - # should have lowest numbers. - # 100 is a good default when no collision is expected. - rule_priority = number - - from_port = number - to_port = number - protocol = string - }) -} - -variable "configuration" { - description = "Container configuration options" - - type = object({ - - environment = list(object({ - name = string - value = string - })) - secrets = list(object({ - name = string - valueFrom = string - })) - }) -} - -variable "command" { - description = "Command to run when the container starts. Overrides the CMD specified by the Dockerfile" - # Empty array will just run default CMD defined in Dockerfile - default = [] - type = list(string) -} - -variable "health_check_path" { - description = "A path to an endpoint on the container suitable for use as a health check" - type = string - default = null -} diff --git a/infrastructure/terraform/modules/core-services/README.md b/infrastructure/terraform/modules/core-services/README.md deleted file mode 100644 index b918e43d41..0000000000 --- a/infrastructure/terraform/modules/core-services/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Setup - -In a `main.tf` file for a workspace that needs a cluster, -you can use this module like: - -``` -module "cluster" { - source = "../path/to/this/directory" - // version is disallowed when using path-based modules - - environment = "staging" - region = "us-east-2" - hosted_zone_id = "SOME-ZONE-ID" - // all other variables are optional -} -``` - -then - -``` -terraform init -terraform apply -``` - -You will see these resources under `module.cluster.xyz`. - -## Managing the ECS Task Definition - -Working with ECS task definitions in Terraform -is kind of awkward. - -This module creates an ECS task definition, -so that it can set up the ECS service that uses that task definition, -but expects that future task revisions will be created by a CI pipeline -as new images are created and pushed. -The `deploy_on_merge.yml` has an example of such a pipeline. -In this pipeline, -we get the ECS task definition from a file, -and interpolate the image, -to create a new revision. -Terraform ignores changes -made by the pipeline, -due to the `lifecycle` setting -on the ECS service resource. - -If you make changes to the task definition resource in this module, -and run `terraform apply` in the `ecs-staging` directory, -Terraform will update the task, -but the next time a new commit is pushed to git, -that change will be overwritten -by the definition in the `ecs-staging` directory, -that's used by the pipeline. - -Ideally these definitions would come from the same source -so if you're reading this -perhaps today is the day -to make that refactor! - -More information about the general wonkiness -of managing ECS with Terraform -can be found in [this Terraform issue.](https://github.com/hashicorp/terraform-provider-aws/issues/632) - -## Rotating the RDS Password - -The RDS password is retrieved from AWS Secrets Manager -but that password is managed manually, -and rotating it requires downtime. - -To rotate it, you'll need to perform the following steps: - -- Update the value of the Secrets Manager entry through the AWS console -- Update the value in the RDS instance through the AWS console. (At this point, the core container will stop being able to access the database.) -- Recreate the core container's service with `aws update-service cluster $CLUSTER_NAME --service $SERVICE_NAME --force-new-deployment` - -In the future the RDS should probably [manage its own password](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-secrets-manager.html) -which will probably require changing the service's code -to fetch the password from Secrets Manager itself -rather than getting it passed in from an environment variable. - -## Rotating the ACM Certificate - -ACM issues certs that last for 1 year. They send you an email prior to renewing, but will automatically rotate the cert. - -Since we rely on DNS validation, it is necessary that our validation CNAMEs are present in route53, provided by this module. -The required records MAY not change, but if they have changed behind the scenes, this will catch up our terraform state: - -```bash -# from clean state , no changes to code - -# updates our state file's records of the Domain Validation Options on the cert (DVOs). -terraform apply - -# should show that new DNS records need to be created/updated, matching the DVOs. -terraform plan -out TMP.tfplan -terraform apply TMP.tfplan -``` - -For more info: see [AWS Docs](https://docs.aws.amazon.com/acm/latest/userguide/dns-renewal-validation.html). - -## Development - -When you change the resources in this directory, you must run `terraform apply` in the calling workspace to see changes. - -More info on developing [terraform modules](https://developer.hashicorp.com/terraform/language/modules/develop). diff --git a/infrastructure/terraform/modules/core-services/main.tf b/infrastructure/terraform/modules/core-services/main.tf deleted file mode 100644 index 9160fa4da6..0000000000 --- a/infrastructure/terraform/modules/core-services/main.tf +++ /dev/null @@ -1,329 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 0.12.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.0" - } - } -} - -# Static secrets -resource "random_password" "api_key" { - length = 32 - special = true - override_special = "-_.~!#$&'()*+,/:;=?@[]" -} - -resource "aws_secretsmanager_secret" "api_key" { - name = "api-key-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret_version" "api_key" { - secret_id = aws_secretsmanager_secret.api_key.id - secret_string = random_password.api_key.result -} - -resource "aws_secretsmanager_secret" "honeycomb_api_key" { - name = "honeycombio-apikey-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret" "gcloud_key_file" { - name = "gcloud-key-file-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret" "datacite_repository_id" { - name = "datacite-repository-id-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret" "datacite_password" { - name = "datacite-password-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -# generate password and make it accessible through aws secrets manager -resource "random_password" "rds_db_password" { - length = 16 - special = false -} - -resource "aws_secretsmanager_secret" "rds_db_password" { - name = "rds-db-password-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret_version" "password" { - secret_id = aws_secretsmanager_secret.rds_db_password.id - secret_string = random_password.rds_db_password.result -} - -# network config -resource "aws_db_subnet_group" "ecs_dbs" { - name = "${var.cluster_info.name}_ecs_db_${var.cluster_info.environment}" - subnet_ids = var.cluster_info.private_subnet_ids - - tags = { - Name = "subnet group for ECS RDS instances" - } -} - -resource "aws_security_group" "ecs_tasks_rds_instances" { - name = "${var.cluster_info.name}-sg-rds-${var.cluster_info.environment}" - vpc_id = var.cluster_info.vpc_id - - ingress { - protocol = "tcp" - from_port = 5432 - to_port = 5432 - security_groups = var.cluster_info.container_security_group_ids - } -} - -resource "aws_elasticache_subnet_group" "core_valkey" { - name = "${var.cluster_info.name}-core-valkey-${var.cluster_info.environment}" - subnet_ids = var.cluster_info.private_subnet_ids - - tags = { - Name = "subnet group for core valkey cache instances" - } -} - -resource "aws_security_group" "core_valkey" { - name = "${var.cluster_info.name}-sg-core-valkey-${var.cluster_info.environment}" - vpc_id = var.cluster_info.vpc_id - - ingress { - protocol = "tcp" - from_port = 6379 - to_port = 6379 - security_groups = var.cluster_info.container_security_group_ids - } -} - -# cache service for core app -resource "aws_elasticache_replication_group" "core_valkey" { - replication_group_id = "${var.cluster_info.name}-core-valkey-${var.cluster_info.environment}" - description = "Core cache instance" - node_type = "cache.t4g.medium" - engine = "valkey" - subnet_group_name = aws_elasticache_subnet_group.core_valkey.name - - num_cache_clusters = 1 - parameter_group_name = "default.valkey8" - port = 6379 - security_group_ids = [aws_security_group.core_valkey.id] -} - -# resource "aws_elasticache_parameter_group" "core_valkey" { -# name = "core-valkey-params" -# family = "valkey8" -# parameter { -# } -# } - -# the actual database instance -resource "aws_db_instance" "core_postgres" { - identifier = "${var.cluster_info.name}-core-postgres-${var.cluster_info.environment}" - allocated_storage = 20 - db_name = "${var.cluster_info.name}_${var.cluster_info.environment}_core_postgres" - db_subnet_group_name = aws_db_subnet_group.ecs_dbs.name - engine = "postgres" - engine_version = "14" - instance_class = "db.t3.small" - vpc_security_group_ids = [aws_security_group.ecs_tasks_rds_instances.id] - username = var.cluster_info.name - password = random_password.rds_db_password.result - parameter_group_name = "default.postgres14" - - backup_retention_period = 7 - backup_window = "03:00-04:00" - maintenance_window = "mon:04:00-mon:05:00" - copy_tags_to_snapshot = true - skip_final_snapshot = false - final_snapshot_identifier = "${var.cluster_info.name}-core-postgres-${var.cluster_info.environment}-final-snapshot" -} - -# see https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/v4.1.0 -module "assets_bucket" { - source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 4.0" - - block_public_acls = false - block_public_policy = false - ignore_public_acls = false - restrict_public_buckets = false - bucket = var.assets_bucket_url_name - control_object_ownership = true - object_ownership = "ObjectWriter" - acl = "public-read" - - cors_rule = [{ - allowed_headers = ["*"] - allowed_methods = ["PUT", "GET", "HEAD", "POST"] - allowed_origins = ["*"] - expose_headers = ["ETag", "Location"] - max_age_seconds = 3000 - }] - - lifecycle_rule = [ - { - id = "expire-temporary" - enabled = true - filter = { - prefix = "temporary/" - } - expiration = { - days = 90 - } - }] -} - -# cloudfront for serving static sites from s3 -# sites are uploaded to the assets bucket under the /sites prefix -resource "aws_cloudfront_origin_access_control" "sites" { - name = "${var.cluster_info.name}-sites-oac-${var.cluster_info.environment}" - description = "OAC for static sites" - origin_access_control_origin_type = "s3" - signing_behavior = "always" - signing_protocol = "sigv4" -} - -resource "aws_cloudfront_distribution" "sites" { - enabled = true - default_root_object = "index.html" - comment = "Static sites distribution for ${var.cluster_info.name}-${var.cluster_info.environment}" - - origin { - domain_name = module.assets_bucket.s3_bucket_bucket_regional_domain_name - origin_id = "S3-sites" - origin_path = "/sites" - origin_access_control_id = aws_cloudfront_origin_access_control.sites.id - } - - default_cache_behavior { - allowed_methods = ["GET", "HEAD", "OPTIONS"] - cached_methods = ["GET", "HEAD"] - target_origin_id = "S3-sites" - viewer_protocol_policy = "redirect-to-https" - compress = true - - forwarded_values { - query_string = false - cookies { - forward = "none" - } - } - - min_ttl = 0 - default_ttl = 3600 - max_ttl = 86400 - } - - # custom error response for spa-style routing (serve index.html for 404s) - custom_error_response { - error_code = 404 - response_code = 200 - response_page_path = "/index.html" - } - - restrictions { - geo_restriction { - restriction_type = "none" - } - } - - viewer_certificate { - cloudfront_default_certificate = true - } - - tags = { - Name = "${var.cluster_info.name}-sites-${var.cluster_info.environment}" - Environment = var.cluster_info.environment - } -} - -# bucket policy to allow cloudfront access to sites prefix -resource "aws_s3_bucket_policy" "sites_cloudfront_access" { - bucket = module.assets_bucket.s3_bucket_id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Sid = "AllowCloudFrontServicePrincipalReadOnly" - Effect = "Allow" - Principal = { - Service = "cloudfront.amazonaws.com" - } - Action = "s3:GetObject" - Resource = "${module.assets_bucket.s3_bucket_arn}/sites/*" - Condition = { - StringEquals = { - "AWS:SourceArn" = aws_cloudfront_distribution.sites.arn - } - } - }, - { - Sid = "PublicReadGetObject" - Effect = "Allow" - Principal = "*" - Action = "s3:GetObject" - Resource = "${module.assets_bucket.s3_bucket_arn}/*" - } - ] - }) -} - -# TODO: replace this with a role-based system for ECS containers -resource "aws_iam_user" "asset_uploader" { - name = "${var.cluster_info.name}-${var.cluster_info.environment}-asset-uploader" - path = "/" -} - -resource "aws_iam_access_key" "asset_uploader" { - user = aws_iam_user.asset_uploader.name -} - -data "aws_iam_policy_document" "asset_uploads" { - statement { - actions = ["s3:PutObject", "s3:GetObject", "s3:PutObjectTagging", "s3:GetObjectTagging", "s3:DeleteObject"] - effect = "Allow" - resources = ["${module.assets_bucket.s3_bucket_arn}/*"] - } -} -resource "aws_iam_policy" "asset_uploads" { - name = "${var.cluster_info.name}-${var.cluster_info.environment}-asset-uploader" - description = "Allow core app to manage files in the assets bucket" - policy = data.aws_iam_policy_document.asset_uploads.json -} - -resource "aws_iam_user_policy_attachment" "attachment_asset_uploader" { - user = aws_iam_user.asset_uploader.name - policy_arn = aws_iam_policy.asset_uploads.arn -} - -resource "aws_secretsmanager_secret" "uploader_iam_secret_key" { - name = "asset-uploader-secret-key-${var.cluster_info.name}-${var.cluster_info.environment}" -} - -resource "aws_secretsmanager_secret_version" "uploader_iam_secret_key" { - secret_id = aws_secretsmanager_secret.uploader_iam_secret_key.id - secret_string = aws_iam_access_key.asset_uploader.secret -} - -## Secrets that must be put into AWS Secrets manager by hand -resource "aws_secretsmanager_secret" "jwt_secret" { - name = "jwt-secret-${var.cluster_info.name}-${var.cluster_info.environment}" -} -resource "aws_secretsmanager_secret" "sentry_auth_token" { - name = "sentry-auth-token-${var.cluster_info.name}-${var.cluster_info.environment}" -} -resource "aws_secretsmanager_secret" "supabase_service_role_key" { - name = "supabase-service-role-key-${var.cluster_info.name}-${var.cluster_info.environment}" -} -resource "aws_secretsmanager_secret" "supabase_webhooks_api_key" { - name = "supabase-webhooks-api-key-${var.cluster_info.name}-${var.cluster_info.environment}" -} -resource "aws_secretsmanager_secret" "mailgun_smtp_password" { - name = "mailgun-smtp-password-${var.cluster_info.name}-${var.cluster_info.environment}" -} diff --git a/infrastructure/terraform/modules/core-services/outputs.tf b/infrastructure/terraform/modules/core-services/outputs.tf deleted file mode 100644 index 686e2a4d53..0000000000 --- a/infrastructure/terraform/modules/core-services/outputs.tf +++ /dev/null @@ -1,52 +0,0 @@ -locals { - db_user = aws_db_instance.core_postgres.username - db_name = aws_db_instance.core_postgres.db_name - db_host = aws_db_instance.core_postgres.address -} - -output "secrets" { - value = { - api_key = aws_secretsmanager_secret.api_key.id - asset_uploader_secret_key = aws_secretsmanager_secret.uploader_iam_secret_key.id - rds_db_password = aws_secretsmanager_secret.rds_db_password.id - jwt_secret = aws_secretsmanager_secret.jwt_secret.id - honeycomb_api_key = aws_secretsmanager_secret.honeycomb_api_key.id - sentry_auth_token = aws_secretsmanager_secret.sentry_auth_token.id - supabase_service_role_key = aws_secretsmanager_secret.supabase_service_role_key.id - supabase_webhooks_api_key = aws_secretsmanager_secret.supabase_webhooks_api_key.id - mailgun_smtp_password = aws_secretsmanager_secret.mailgun_smtp_password.id - gcloud_key_file = aws_secretsmanager_secret.gcloud_key_file.id - datacite_repository_id = aws_secretsmanager_secret.datacite_repository_id.id - datacite_password = aws_secretsmanager_secret.datacite_password.id - } -} - -output "asset_uploader_key_id" { - value = aws_iam_access_key.asset_uploader.id -} - -output "rds_connection_components" { - value = { - user = local.db_user - database = local.db_name - host = local.db_host - port = "5432" - id = aws_db_instance.core_postgres.id - } -} - -output "valkey_host" { - value = aws_elasticache_replication_group.core_valkey.primary_endpoint_address -} - -output "sites_cloudfront_domain" { - value = aws_cloudfront_distribution.sites.domain_name -} - -output "sites_cloudfront_distribution_id" { - value = aws_cloudfront_distribution.sites.id -} - -output "sites_base_url" { - value = "https://${aws_cloudfront_distribution.sites.domain_name}" -} diff --git a/infrastructure/terraform/modules/core-services/variables.tf b/infrastructure/terraform/modules/core-services/variables.tf deleted file mode 100644 index 7e3ebc0556..0000000000 --- a/infrastructure/terraform/modules/core-services/variables.tf +++ /dev/null @@ -1,20 +0,0 @@ -variable "cluster_info" { - description = "infrastructure values output from v7-cluster" - - type = object({ - region = string - name = string - vpc_id = string - cluster_arn = string - environment = string - private_subnet_ids = list(string) - container_security_group_ids = list(string) - cloudwatch_log_group_name = string - lb_listener_arn = string - }) -} - -variable "assets_bucket_url_name" { - description = "Name for the asset bucket -- typically a domain like assets.v7.pubpub.org" - type = string -} diff --git a/infrastructure/terraform/modules/deployment/main.tf b/infrastructure/terraform/modules/deployment/main.tf deleted file mode 100644 index e08f9a1e55..0000000000 --- a/infrastructure/terraform/modules/deployment/main.tf +++ /dev/null @@ -1,250 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 1.5.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.0" - } - - honeycombio = { - source = "honeycombio/honeycombio" - version = ">= 0.22.0" - } - } -} - -# provider "aws" { -# region = var.region -# } - -module "cluster" { - source = "../v7-cluster" - - name = var.name - environment = var.environment - region = var.region - - pubpub_hostname = var.pubpub_hostname - route53_zone_id = var.route53_zone_id - - container_ingress_port = 8080 - - availability_zones = ["us-east-1a", "us-east-1c"] -} - -module "core_dependency_services" { - source = "../core-services" - - cluster_info = module.cluster.cluster_info - assets_bucket_url_name = var.ASSETS_BUCKET_NAME -} - -locals { - PUBPUB_URL = "https://${var.pubpub_hostname}" -} - -module "service_core" { - source = "../container-generic" - - service_name = "core" - cluster_info = module.cluster.cluster_info - - repository_url = var.ecr_repository_urls.core - nginx_image = "${var.ecr_repository_urls.nginx}:latest" - health_check_path = "/api/health" - - listener = { - service_name = "core" - public = true - path_prefix = "/" - rule_priority = 100 - from_port = 3000 - to_port = 3000 - protocol = "tcp" - } - - init_containers = [{ - name = "migrations" - image = "${var.ecr_repository_urls.root}:latest" - command = [ - "pnpm", "--filter", "core", "migrate-docker", - ] - }] - - resources = { - cpu = 1024 - memory = 2048 - desired_count = 1 - } - - configuration = { - container_port = 3000 - environment = [ - { name = "PGUSER", value = module.core_dependency_services.rds_connection_components.user }, - { name = "PGDATABASE", value = module.core_dependency_services.rds_connection_components.database }, - { name = "PGHOST", value = module.core_dependency_services.rds_connection_components.host }, - { name = "PGPORT", value = module.core_dependency_services.rds_connection_components.port }, - { name = "ASSETS_REGION", value = var.region }, - { name = "ASSETS_BUCKET_NAME", value = var.ASSETS_BUCKET_NAME }, - { name = "ASSETS_UPLOAD_KEY", value = module.core_dependency_services.asset_uploader_key_id }, - { name = "MAILGUN_SMTP_USERNAME", value = var.MAILGUN_SMTP_USERNAME }, - { name = "MAILGUN_SMTP_HOST", value = var.MAILGUN_SMTP_HOST }, - { name = "MAILGUN_SMTP_PORT", value = var.MAILGUN_SMTP_PORT }, - { name = "PUBPUB_URL", value = local.PUBPUB_URL }, - { name = "HOSTNAME", value = var.HOSTNAME }, - { name = "DATACITE_API_URL", value = var.DATACITE_API_URL }, - { name = "VALKEY_HOST", value = module.core_dependency_services.valkey_host }, - { name = "SITE_BUILDER_ENDPOINT", value = local.PUBPUB_URL }, - { name = "SITES_BASE_URL", value = module.core_dependency_services.sites_base_url } - ] - - secrets = [ - { name = "PGPASSWORD", valueFrom = module.core_dependency_services.secrets.rds_db_password }, - { name = "API_KEY", valueFrom = module.core_dependency_services.secrets.api_key }, - { name = "JWT_SECRET", valueFrom = module.core_dependency_services.secrets.jwt_secret }, - { name = "ASSETS_UPLOAD_SECRET_KEY", valueFrom = module.core_dependency_services.secrets.asset_uploader_secret_key }, - { name = "SENTRY_AUTH_TOKEN", valueFrom = module.core_dependency_services.secrets.sentry_auth_token }, - { name = "SUPABASE_WEBHOOKS_API_KEY", valueFrom = module.core_dependency_services.secrets.supabase_webhooks_api_key }, - { name = "SUPABASE_SERVICE_ROLE_KEY", valueFrom = module.core_dependency_services.secrets.supabase_service_role_key }, - { name = "HONEYCOMB_API_KEY", valueFrom = module.core_dependency_services.secrets.honeycomb_api_key }, - { name = "MAILGUN_SMTP_PASSWORD", valueFrom = module.core_dependency_services.secrets.mailgun_smtp_password }, - { name = "GCLOUD_KEY_FILE", valueFrom = module.core_dependency_services.secrets.gcloud_key_file }, - { name = "DATACITE_REPOSITORY_ID", valueFrom = module.core_dependency_services.secrets.datacite_repository_id }, - { name = "DATACITE_PASSWORD", valueFrom = module.core_dependency_services.secrets.datacite_password }, - ] - } -} - -module "service_flock" { - source = "../container-generic" - - service_name = "jobs" - cluster_info = module.cluster.cluster_info - - repository_url = var.ecr_repository_urls.jobs - - configuration = { - container_port = 3000 - environment = [ - { name = "PUBPUB_URL", value = local.PUBPUB_URL }, - { name = "PGUSER", value = module.core_dependency_services.rds_connection_components.user }, - { name = "PGDATABASE", value = module.core_dependency_services.rds_connection_components.database }, - { name = "PGHOST", value = module.core_dependency_services.rds_connection_components.host }, - { name = "PGPORT", value = module.core_dependency_services.rds_connection_components.port }, - ] - - secrets = [ - { name = "PGPASSWORD", valueFrom = module.core_dependency_services.secrets.rds_db_password }, - { name = "API_KEY", valueFrom = module.core_dependency_services.secrets.api_key }, - { name = "HONEYCOMB_API_KEY", valueFrom = module.core_dependency_services.secrets.honeycomb_api_key }, - ] - } -} - -module "service_bastion" { - source = "../container-generic" - - service_name = "bastion" - cluster_info = module.cluster.cluster_info - - repository_url = var.ecr_repository_urls.root - # Make bastion idle indefinitely, so we can ssh into it when needed - # If this is not here, the task will exit and try to restart immediately. - # TODO: Maybe there's a less hacky way to do this? - command = ["sh", "-c", "trap : TERM INT; sleep infinity & wait"] - - configuration = { - environment = [ - { name = "PGUSER", value = module.core_dependency_services.rds_connection_components.user }, - { name = "PGDATABASE", value = module.core_dependency_services.rds_connection_components.database }, - { name = "PGHOST", value = module.core_dependency_services.rds_connection_components.host }, - { name = "PGPORT", value = module.core_dependency_services.rds_connection_components.port }, - { name = "HOSTNAME", value = var.HOSTNAME }, - { name = "PAGER", value = "less -S" }, - { name = "VALKEY_HOST", value = module.core_dependency_services.valkey_host } - ] - - secrets = [ - { name = "PGPASSWORD", valueFrom = module.core_dependency_services.secrets.rds_db_password }, - - # Bastion needs supabase creds in case of seed script - { name = "SUPABASE_SERVICE_ROLE_KEY", valueFrom = module.core_dependency_services.secrets.supabase_service_role_key }, - ] - } - - resources = { - cpu = 1024 - memory = 2048 # need slightly beefier machine for the bastion - - # TODO: disable autoscaling, which makes no sense for a bastion - desired_count = 1 - } -} - -module "service_site_builder" { - source = "../container-generic" - - service_name = "site-builder" - cluster_info = module.cluster.cluster_info - repository_url = var.ecr_repository_urls.site_builder - nginx_image = "${var.ecr_repository_urls.nginx}:latest" - - resources = { - cpu = 1024 - memory = 2048 - desired_count = 1 - } - - listener = { - service_name = "site-builder" - public = true - path_prefix = "/services/site-builder/" - rule_priority = 80 - from_port = 4000 - to_port = 4000 - protocol = "tcp" - } - - configuration = { - container_port = 4000 - - environment = [ - { name = "PUBPUB_URL", value = local.PUBPUB_URL }, - { name = "PORT", value = 4000 }, - { name = "S3_ACCESS_KEY", value = module.core_dependency_services.asset_uploader_key_id }, - { name = "S3_BUCKET_NAME", value = var.ASSETS_BUCKET_NAME }, - { name = "S3_REGION", value = var.region }, - // don't need to set S3_ENDPOINT, if empty will use s3 - ] - - secrets = [ - { name = "S3_SECRET_KEY", valueFrom = module.core_dependency_services.secrets.asset_uploader_secret_key }, - ] - } -} - - -# N.B. This invocation means that the deployment including honeycomb cannot succeed -# until after you have inserted the secret into the AWS console. This only happens -# in this one case because with things like ECS, you can successfully "apply" -# even if secrets are not present; the containers will simply fail to start. -# However, this last section of TF code can be commented out for a first apply, -# then go and insert secret in console, then reapply with this. -# -# This is the result of an awkward design pattern, where instead of the -# Honeycomb provider being configured to search for an API key in the env, -# the modules themselves expect an API key as an inline var and fail if -# it is not set. This is probably because the API keys are different for -# different environments, rather than per account/user/etc. -data "aws_secretsmanager_secret_version" "honeycomb_api_key" { - secret_id = module.core_dependency_services.secrets.honeycomb_api_key -} - -module "observability_honeycomb_integration" { - source = "../honeycomb-integration" - - cluster_info = module.cluster.cluster_info - HONEYCOMB_API_KEY = data.aws_secretsmanager_secret_version.honeycomb_api_key.secret_string -} diff --git a/infrastructure/terraform/modules/deployment/outputs.tf b/infrastructure/terraform/modules/deployment/outputs.tf deleted file mode 100644 index 9fe94c2d40..0000000000 --- a/infrastructure/terraform/modules/deployment/outputs.tf +++ /dev/null @@ -1,3 +0,0 @@ -output "cluster_info" { - value = module.cluster.cluster_info -} diff --git a/infrastructure/terraform/modules/deployment/variables.tf b/infrastructure/terraform/modules/deployment/variables.tf deleted file mode 100644 index 78b253711b..0000000000 --- a/infrastructure/terraform/modules/deployment/variables.tf +++ /dev/null @@ -1,73 +0,0 @@ -variable "region" { - description = "AWS region shortname" - type = string - default = "us-east-1" -} - -variable "name" { - description = "Proper name for this environment" - type = string -} - -variable "environment" { - description = "Functional name for this environment" - type = string -} - -variable "pubpub_hostname" { - description = "hostname where pubpub will be addressable (DO NOT include https://)" - type = string -} - -variable "site_builder_hostname" { - description = "hostname where site builder will be addressable (DO NOT include https://)" - type = string -} - -variable "route53_zone_id" { - description = "Zone ID of route53 zone that is already configured as the NS for your subdomain" - type = string -} - -variable "ecr_repository_urls" { - description = "URLs for ECR repositories created at a global level" - type = object({ - core = string - jobs = string - nginx = string - root = string - site_builder = string - }) -} - -variable "MAILGUN_SMTP_USERNAME" { - description = "SMTP Username for Mailgun service" - type = string -} - -variable "MAILGUN_SMTP_HOST" { - description = "SMTP Hostname for Mailgun service" - type = string - default = "smtp.mailgun.org" -} - -variable "MAILGUN_SMTP_PORT" { - description = "SMTP Network Port for Mailgun service" - type = string - default = "465" -} - -variable "HOSTNAME" { - description = "Hostname used by standalone Next app" -} - -# TODO deprecate this in favor of a Terraformed bucket -variable "ASSETS_BUCKET_NAME" { - description = "Name of the S3 bucket to store assets" - type = string -} - -variable "DATACITE_API_URL" { - description = "DataCite API URL used by the DataCite action to deposit pubs and allocate DOIs" - type = string -} diff --git a/infrastructure/terraform/modules/ecr-repositories/main.tf b/infrastructure/terraform/modules/ecr-repositories/main.tf deleted file mode 100644 index 52e36fbc30..0000000000 --- a/infrastructure/terraform/modules/ecr-repositories/main.tf +++ /dev/null @@ -1,60 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 0.12.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.0" - } - } -} - - -# ecr repositories for all containers -resource "aws_ecr_repository" "pubpub_v7" { - name = "pubpub-v7" - image_tag_mutability = "MUTABLE" - - image_scanning_configuration { - scan_on_push = false # can set this to true if we want - } -} - -resource "aws_ecr_repository" "pubpub_v7_core" { - name = "pubpub-v7-core" - image_tag_mutability = "MUTABLE" - - image_scanning_configuration { - scan_on_push = false # can set this to true if we want - } -} - -resource "aws_ecr_repository" "pubpub_v7_jobs" { - name = "pubpub-v7-jobs" - image_tag_mutability = "MUTABLE" - - image_scanning_configuration { - scan_on_push = false # can set this to true if we want - } -} - -resource "aws_ecr_repository" "pubpub_v7_site_builder" { - name = "pubpub-v7-site-builder" - image_tag_mutability = "MUTABLE" - - image_scanning_configuration { - scan_on_push = false # can set this to true if we want - } -} - -# tiny image that just removes the a path prefix -resource "aws_ecr_repository" "nginx" { - name = "nginx" - image_tag_mutability = "MUTABLE" - - image_scanning_configuration { - scan_on_push = false # can set this to true if we want - } -} - diff --git a/infrastructure/terraform/modules/ecr-repositories/outputs.tf b/infrastructure/terraform/modules/ecr-repositories/outputs.tf deleted file mode 100644 index c8d3647f46..0000000000 --- a/infrastructure/terraform/modules/ecr-repositories/outputs.tf +++ /dev/null @@ -1,10 +0,0 @@ -output "ecr_repository_urls" { - value = { - core = aws_ecr_repository.pubpub_v7_core.repository_url - jobs = aws_ecr_repository.pubpub_v7_jobs.repository_url - site-builder = aws_ecr_repository.pubpub_v7_site_builder.repository_url - nginx = aws_ecr_repository.nginx.repository_url - root = aws_ecr_repository.pubpub_v7.repository_url - } -} - diff --git a/infrastructure/terraform/modules/ecr-repositories/variables.tf b/infrastructure/terraform/modules/ecr-repositories/variables.tf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/terraform/modules/honeycomb-integration/README.md b/infrastructure/terraform/modules/honeycomb-integration/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/terraform/modules/honeycomb-integration/main.tf b/infrastructure/terraform/modules/honeycomb-integration/main.tf deleted file mode 100644 index 44fc3f8f1e..0000000000 --- a/infrastructure/terraform/modules/honeycomb-integration/main.tf +++ /dev/null @@ -1,48 +0,0 @@ -locals { - failure_logs_bucket_name = "pubpub-v7-${var.cluster_info.name}-${var.cluster_info.environment}-honeycomb-tf-integrations-failures" -} - -# see https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/v4.1.0 -module "firehose_failure_bucket" { - source = "terraform-aws-modules/s3-bucket/aws" - version = "~> 4.0" - - bucket = local.failure_logs_bucket_name - force_destroy = true - transition_default_minimum_object_size = "varies_by_storage_class" - - - lifecycle_rule = [{ - id = "expiration-30-days" - enabled = true - expiration = { - days = 30 - } - }] -} - -module "honeycomb-aws-cloudwatch-metrics-integration" { - source = "honeycombio/integrations/aws//modules/cloudwatch-metrics" - - name = "${var.cluster_info.name}-${var.cluster_info.environment}-cw-metrics" - - honeycomb_api_key = var.HONEYCOMB_API_KEY // Honeycomb API key. - honeycomb_dataset_name = "cloudwatch-metrics" // Your Honeycomb dataset name that will receive the metrics. - - s3_failure_bucket_arn = module.firehose_failure_bucket.s3_bucket_arn -} - -# see https://github.com/honeycombio/terraform-aws-integrations/tree/main/modules/cloudwatch-logs -module "honeycomb-aws-cloudwatch-logs-integration" { - source = "honeycombio/integrations/aws//modules/cloudwatch-logs" - - name = "${var.cluster_info.name}-${var.cluster_info.environment}-cw-logs" - - #aws cloudwatch integration - cloudwatch_log_groups = [var.cluster_info.cloudwatch_log_group_name] // CloudWatch Log Group names to stream to Honeycomb. - s3_failure_bucket_arn = module.firehose_failure_bucket.s3_bucket_arn - - #honeycomb - honeycomb_api_key = var.HONEYCOMB_API_KEY // Honeycomb API key. - honeycomb_dataset_name = "cloudwatch-logs" // Your Honeycomb dataset name that will receive the logs. -} diff --git a/infrastructure/terraform/modules/honeycomb-integration/outputs.tf b/infrastructure/terraform/modules/honeycomb-integration/outputs.tf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/infrastructure/terraform/modules/honeycomb-integration/variables.tf b/infrastructure/terraform/modules/honeycomb-integration/variables.tf deleted file mode 100644 index 9a1e897088..0000000000 --- a/infrastructure/terraform/modules/honeycomb-integration/variables.tf +++ /dev/null @@ -1,21 +0,0 @@ -variable "cluster_info" { - description = "infrastructure values output from v7-cluster" - - type = object({ - region = string - name = string - vpc_id = string - cluster_arn = string - environment = string - private_subnet_ids = list(string) - container_security_group_ids = list(string) - cloudwatch_log_group_name = string - lb_listener_arn = string - }) -} - -variable "HONEYCOMB_API_KEY" { - description = "API key for the honeycomb environment" - type = string - sensitive = true -} diff --git a/infrastructure/terraform/modules/v7-cluster/dns.tf b/infrastructure/terraform/modules/v7-cluster/dns.tf deleted file mode 100644 index 98f7f508c7..0000000000 --- a/infrastructure/terraform/modules/v7-cluster/dns.tf +++ /dev/null @@ -1,32 +0,0 @@ -module "alb_certificate" { - source = "terraform-aws-modules/acm/aws" - version = "~> 4.0" - - domain_name = var.pubpub_hostname - zone_id = var.route53_zone_id - - validation_method = "DNS" - - subject_alternative_names = [ - "*.${var.pubpub_hostname}", - ] - - wait_for_validation = true - - tags = { - Name = var.pubpub_hostname - Environment = var.environment - } -} - -resource "aws_route53_record" "alb" { - zone_id = var.route53_zone_id - name = var.pubpub_hostname - type = "A" - - alias { - name = aws_lb.main.dns_name - zone_id = aws_lb.main.zone_id - evaluate_target_health = false - } -} diff --git a/infrastructure/terraform/modules/v7-cluster/ecs.tf b/infrastructure/terraform/modules/v7-cluster/ecs.tf deleted file mode 100644 index a885462f18..0000000000 --- a/infrastructure/terraform/modules/v7-cluster/ecs.tf +++ /dev/null @@ -1,21 +0,0 @@ -module "ecs_cluster" { - source = "terraform-aws-modules/ecs/aws//modules/cluster" - version = "~> 5.0" - - cluster_name = "${var.name}-ecs-cluster-${var.environment}" - - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = "/aws/ecs/aws-ec2" - } - } - } - - tags = { - Environment = "${var.name}-${var.environment}" - Project = "Pubpub-v7" - } -} - diff --git a/infrastructure/terraform/modules/v7-cluster/main.tf b/infrastructure/terraform/modules/v7-cluster/main.tf deleted file mode 100644 index 13c3d56ccb..0000000000 --- a/infrastructure/terraform/modules/v7-cluster/main.tf +++ /dev/null @@ -1,178 +0,0 @@ -# aws terraform provider config - -terraform { - required_version = ">= 0.12.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.0" - } - } -} - -# Network configuration -resource "aws_vpc" "main" { - cidr_block = var.cidr -} - -resource "aws_internet_gateway" "main" { - vpc_id = aws_vpc.main.id -} - -resource "aws_subnet" "private" { - vpc_id = aws_vpc.main.id - cidr_block = element(var.private_subnets, count.index) - availability_zone = element(var.availability_zones, count.index) - count = length(var.private_subnets) -} - -resource "aws_subnet" "public" { - vpc_id = aws_vpc.main.id - cidr_block = element(var.public_subnets, count.index) - availability_zone = element(var.availability_zones, count.index) - count = length(var.public_subnets) - map_public_ip_on_launch = true -} - -resource "aws_route_table" "public" { - vpc_id = aws_vpc.main.id -} - -resource "aws_route" "public" { - route_table_id = aws_route_table.public.id - destination_cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.main.id -} - -resource "aws_route_table_association" "public" { - count = length(var.public_subnets) - subnet_id = element(aws_subnet.public.*.id, count.index) - route_table_id = aws_route_table.public.id -} - -resource "aws_nat_gateway" "main" { - count = length(var.private_subnets) - allocation_id = element(aws_eip.nat.*.id, count.index) - # note that there is one nat gateway per private subnet, - # but the gateway must live in the public subnet - subnet_id = element(aws_subnet.public.*.id, count.index) - depends_on = [aws_internet_gateway.main] -} - -resource "aws_eip" "nat" { - count = length(var.private_subnets) - domain = "vpc" -} - -resource "aws_route_table" "private" { - count = length(var.private_subnets) - vpc_id = aws_vpc.main.id -} - -resource "aws_route" "private" { - count = length(compact(var.private_subnets)) - route_table_id = element(aws_route_table.private.*.id, count.index) - destination_cidr_block = "0.0.0.0/0" - nat_gateway_id = element(aws_nat_gateway.main.*.id, count.index) -} - -resource "aws_route_table_association" "private" { - count = length(var.private_subnets) - subnet_id = element(aws_subnet.private.*.id, count.index) - route_table_id = element(aws_route_table.private.*.id, count.index) -} - -# security groups for the load balancer and task -resource "aws_security_group" "alb" { - name = "${var.name}-sg-alb-${var.environment}" - vpc_id = aws_vpc.main.id - - ingress { - protocol = "tcp" - from_port = 80 - to_port = 80 - cidr_blocks = ["0.0.0.0/0"] - } - - ingress { - protocol = "tcp" - from_port = 443 - to_port = 443 - cidr_blocks = ["0.0.0.0/0"] - } - - egress { - protocol = "-1" - from_port = 0 - to_port = 0 - cidr_blocks = ["0.0.0.0/0"] - } -} - -resource "aws_security_group" "ecs_tasks" { - name = "${var.name}-sg-task-${var.environment}" - vpc_id = aws_vpc.main.id -} - -resource "aws_vpc_security_group_ingress_rule" "ecs_tasks_ingress_alb_all" { - security_group_id = aws_security_group.ecs_tasks.id - referenced_security_group_id = aws_security_group.alb.id - - # allow ALB traffic on all ports. ALB defines access controls. - ip_protocol = -1 -} - -# allow all outbound traffic. -resource "aws_vpc_security_group_egress_rule" "ecs_tasks_egress_ipv4" { - security_group_id = aws_security_group.ecs_tasks.id - cidr_ipv4 = "0.0.0.0/0" - ip_protocol = -1 -} - -resource "aws_vpc_security_group_egress_rule" "ecs_tasks_egress_ipv6" { - security_group_id = aws_security_group.ecs_tasks.id - cidr_ipv6 = "::/0" - ip_protocol = -1 -} - -# load balancer -resource "aws_lb" "main" { - name = "${var.name}-lb-${var.environment}" - internal = false - load_balancer_type = "application" - security_groups = [aws_security_group.alb.id] - subnets = aws_subnet.public.*.id - - enable_deletion_protection = false -} - -resource "aws_lb_listener" "main" { - load_balancer_arn = aws_lb.main.arn - port = 443 - protocol = "HTTPS" - ssl_policy = "ELBSecurityPolicy-2016-08" - certificate_arn = module.alb_certificate.acm_certificate_arn - - - default_action { - type = "fixed-response" - - fixed_response { - content_type = "text/plain" - message_body = "not found" - status_code = "404" - } - } -} - -# TODO - add ACM certificates to support TLS - -# logging - -resource "aws_cloudwatch_log_group" "ecs" { - name = "${var.name}-ecs-${var.environment}-container-logs" - - tags = { - Environment = var.environment - } -} diff --git a/infrastructure/terraform/modules/v7-cluster/outputs.tf b/infrastructure/terraform/modules/v7-cluster/outputs.tf deleted file mode 100644 index 5cf7120c40..0000000000 --- a/infrastructure/terraform/modules/v7-cluster/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -output "cluster_info" { - value = { - region = var.region - name = var.name - vpc_id = aws_vpc.main.id - environment = var.environment - cluster_arn = module.ecs_cluster.arn - private_subnet_ids = aws_subnet.private.*.id - container_security_group_ids = [aws_security_group.ecs_tasks.id] - cloudwatch_log_group_name = aws_cloudwatch_log_group.ecs.name - lb_listener_arn = aws_lb_listener.main.arn - alb_dns_name = aws_lb.main.dns_name - } -} diff --git a/infrastructure/terraform/modules/v7-cluster/variables.tf b/infrastructure/terraform/modules/v7-cluster/variables.tf deleted file mode 100644 index 08f04af57c..0000000000 --- a/infrastructure/terraform/modules/v7-cluster/variables.tf +++ /dev/null @@ -1,70 +0,0 @@ -variable "name" { - description = "Familiar name of the stack" - default = "pubpub" -} - -variable "environment" { - description = "Name of the version/layer of the stack" -} - -variable "cidr" { - description = "The CIDR block for the VPC" - default = "10.0.0.0/16" -} - -variable "private_subnets" { - description = "a list of CIDRs for private subnets in the VPC, one for each availability zone" - default = ["10.0.128.0/20", "10.0.144.0/20"] -} - -variable "public_subnets" { - description = "a list of CIDRs for public subnets in the VPC, one for each availability zone" - default = ["10.0.0.0/20", "10.0.16.0/20"] -} - -variable "availability_zones" { - description = "a list of availability zones" -} - -# variable "container_port" { -# description = "The port the containers are listening on" -# default = 5050 -# } -# -# variable "container_environment" { -# description = "Environment variables for the containers" -# default = [] -# } -# -# variable "health_check_path" { -# description = "The path for the health check" -# default = "/v1/debug/health" -# } -# -# variable "hosted_zone_id" { -# description = "The ID of the hosted zone for the domain" -# } -# -# variable "subdomain" { -# description = "Prefix to domain name of hosted zone above, so serve app from" -# } - -variable "region" { - description = "Region for all resources (MUST agree with provider config)" - default = "us-east-1" -} - -variable "container_ingress_port" { - description = "port to allow traffic in private security group" - type = number -} - -variable "pubpub_hostname" { - description = "domain name where this will be served by ALB" - type = string -} - -variable "route53_zone_id" { - description = "Zone ID of route53 zone that is already configured as the NS for your subdomain" - type = string -} diff --git a/jobs/.env.development b/jobs/.env.development index c998c90202..c30fbc16ff 100644 --- a/jobs/.env.development +++ b/jobs/.env.development @@ -1,3 +1,9 @@ API_KEY="super_secret_key" DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres" -PUBPUB_URL="http://localhost:3000" \ No newline at end of file +PUBSTAR_HOSTNAME="http://localhost:3000" +S3_BACKUP_BUCKET=backups.pubstar.org +S3_BACKUP_ACCESS_KEY=pubstarbackupuser +S3_BACKUP_SECRET_KEY=pubstarbackuppass +S3_BACKUP_REGION=us-east-1 +S3_BACKUP_ENDPOINT=http://localhost:9000 +S3_BACKUP_KEY_PREFIX=pg-backups \ No newline at end of file diff --git a/jobs/package.json b/jobs/package.json index e3e7f179ad..37b8b19f95 100644 --- a/jobs/package.json +++ b/jobs/package.json @@ -11,22 +11,30 @@ }, "files": ["src"], "dependencies": { + "@aws-sdk/client-s3": "^3.1038.0", + "@aws-sdk/lib-storage": "^3.1038.0", "@honeycombio/opentelemetry-node": "catalog:", "@opentelemetry/auto-instrumentations-node": "catalog:", + "@sentry/node": "^10.50.0", "@ts-rest/core": "catalog:", "contracts": "workspace:*", "db": "workspace:*", "graphile-worker": "^0.16.6", + "kysely": "^0.27.6", "logger": "workspace:*", + "nodemailer": "^6.10.1", + "pg": "^8.16.3", "react": "catalog:react19", "tsx": "catalog:", "zod": "catalog:" }, "devDependencies": { "@types/node": "catalog:", + "@types/nodemailer": "^6.4.18", + "@types/pg": "^8.15.5", + "@typescript/native-preview": "catalog:", "dotenv-cli": "^7.4.4", "tsconfig": "workspace:*", - "typescript": "catalog:", - "@typescript/native-preview": "catalog:" + "typescript": "catalog:" } } diff --git a/jobs/src/clients.ts b/jobs/src/clients.ts index 71b9a4e7b5..5b0cc07606 100644 --- a/jobs/src/clients.ts +++ b/jobs/src/clients.ts @@ -3,7 +3,7 @@ import { initClient } from "@ts-rest/core" import { api } from "contracts" export const internalClient = initClient(api.internal, { - baseUrl: `${process.env.PUBPUB_URL}`, + baseUrl: `${process.env.PUBSTAR_URL}`, baseHeaders: { authorization: `Bearer ${process.env.API_KEY}` }, jsonQuery: true, }) diff --git a/jobs/src/database.ts b/jobs/src/database.ts new file mode 100644 index 0000000000..583b894fc9 --- /dev/null +++ b/jobs/src/database.ts @@ -0,0 +1,25 @@ +import type { BackupConfigTable, BackupRecordsTable } from "db/public" + +import { Kysely, PostgresDialect } from "kysely" +import pg from "pg" + +const int8TypeId = 20 +pg.types.setTypeParser(int8TypeId, (val) => parseInt(val, 10)) + +export interface BackupDatabase { + backup_config: BackupConfigTable + backup_records: BackupRecordsTable +} + +export const createBackupDatabase = (connectionString: string) => { + const pool = new pg.Pool({ + connectionString, + max: 2, + }) + + const db = new Kysely({ + dialect: new PostgresDialect({ pool }), + }) + + return { db, pool } +} diff --git a/jobs/src/index.ts b/jobs/src/index.ts index 9ee2c02829..f7d0d7195a 100644 --- a/jobs/src/index.ts +++ b/jobs/src/index.ts @@ -1,25 +1,71 @@ import type { TaskList } from "graphile-worker" import { run } from "graphile-worker" +import pg from "pg" import { logger } from "logger" import { clients } from "./clients" +import { createBackup } from "./jobs/createBackup" import { emitEvent } from "./jobs/emitEvent" +const ADVISORY_LOCK_ID = 72_398_241 + const makeTaskList = (client: typeof clients): TaskList => ({ emitEvent: emitEvent(client.internalClient), + createBackup, }) +async function waitForMigrations(connectionString: string, maxAttempts = 60, intervalMs = 3000) { + const pool = new pg.Pool({ connectionString, max: 1 }) + const stack = new AsyncDisposableStack() + stack.defer(async () => pool.end()) + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const client = await pool.connect() + + try { + await client.query(`SELECT pg_advisory_lock(${ADVISORY_LOCK_ID})`) + await client.query(`SELECT pg_advisory_unlock(${ADVISORY_LOCK_ID})`) + logger.info("migration lock is free, proceeding with worker startup") + return + } finally { + client.release() + } + } catch { + logger.info(`waiting for migrations to complete (${attempt}/${maxAttempts})...`) + await new Promise((r) => setTimeout(r, intervalMs)) + } + } + + logger.warn("timed out waiting for migration lock, starting anyway") +} + const main = async () => { + const connectionString = process.env.DATABASE_URL + if (!connectionString) { + logger.error("DATABASE_URL is required") + process.exit(1) + } + + await waitForMigrations(connectionString) + logger.info("Starting graphile worker...") + + // fires createBackup every hour on the hour. the job itself reads + // backup_config to decide whether enough time has elapsed since the + // last successful backup before actually running. + const backupCrontab = `0 * * * * createBackup ?id=scheduled-backup&fill=2h` + try { const runner = await run({ - connectionString: process.env.DATABASE_URL, + connectionString, concurrency: 5, noHandleSignals: false, pollInterval: 1000, taskList: makeTaskList(clients), + crontab: backupCrontab, }) logger.info({ msg: `Successfully started graphile worker`, runner }) diff --git a/jobs/src/jobs/createBackup.ts b/jobs/src/jobs/createBackup.ts new file mode 100644 index 0000000000..8ffb59df4b --- /dev/null +++ b/jobs/src/jobs/createBackup.ts @@ -0,0 +1,391 @@ +import type { JobHelpers } from "graphile-worker" +import type { BackupDatabase } from "../database" + +import { execFile } from "node:child_process" +import { createReadStream } from "node:fs" +import { stat, unlink } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" +import { promisify } from "node:util" +import { DeleteObjectsCommand, type ObjectIdentifier, S3Client } from "@aws-sdk/client-s3" +import { Upload } from "@aws-sdk/lib-storage" +import * as Sentry from "@sentry/node" +import nodemailer from "nodemailer" + +import { type BackupRecordsId, BackupStatus } from "db/public" +import { logger } from "logger" + +import { createBackupDatabase } from "../database" + +const execFileAsync = promisify(execFile) + +const DEFAULT_BACKUP_PREFIX = "pg-backups" +const DEFAULT_BACKUP_INTERVAL_HOURS = 24 +const DEFAULT_BACKUP_RETENTION_DAYS = 14 + +type CreateBackupPayload = { + backupId?: string +} + +type BackupS3Config = { + bucket: string + region: string + accessKey: string + secretKey: string + endpoint?: string + keyPrefix: string +} + +const ensureSentryInitialized = () => { + const dsn = process.env.SENTRY_DSN + if (!dsn) { + return false + } + + Sentry.init({ dsn }) + return true +} + +const getBackupS3Config = (): BackupS3Config => { + const bucket = process.env.S3_BACKUP_BUCKET + const region = process.env.S3_BACKUP_REGION + const accessKey = process.env.S3_BACKUP_ACCESS_KEY + const secretKey = process.env.S3_BACKUP_SECRET_KEY + const endpoint = process.env.S3_BACKUP_ENDPOINT + const keyPrefix = process.env.S3_BACKUP_KEY_PREFIX ?? DEFAULT_BACKUP_PREFIX + + const isMissingRequiredBackupEnv = !bucket || !region || !accessKey || !secretKey + if (isMissingRequiredBackupEnv) { + throw new Error("Missing S3 backup configuration variables") + } + + return { + bucket, + region, + accessKey, + secretKey, + endpoint, + keyPrefix, + } +} + +const getBackupConfig = async (db: import("kysely").Kysely) => { + const config = await db + .selectFrom("backup_config") + .selectAll() + .orderBy("updatedAt", "desc") + .limit(1) + .executeTakeFirst() + + if (config) { + return config + } + + return { + enabled: false, + intervalHours: DEFAULT_BACKUP_INTERVAL_HOURS, + retentionDays: DEFAULT_BACKUP_RETENTION_DAYS, + notificationEmail: null as string | null, + } +} + +const shouldRunScheduledBackup = async (db: import("kysely").Kysely) => { + const config = await getBackupConfig(db) + + if (!config.enabled) { + return { shouldRun: false, reason: "backup is disabled", config } as const + } + + const lastCompleted = await db + .selectFrom("backup_records") + .select("completedAt") + .where("status", "=", BackupStatus.completed) + .orderBy("completedAt", "desc") + .limit(1) + .executeTakeFirst() + + if (!lastCompleted?.completedAt) { + return { shouldRun: true, config } as const + } + + const intervalMs = config.intervalHours * 60 * 60 * 1000 + const elapsed = Date.now() - new Date(lastCompleted.completedAt).getTime() + + const shouldRun = elapsed >= intervalMs + return { shouldRun, config, reason: shouldRun ? "backup is due" : "backup is not due" } as const +} + +const upsertBackupRecordForRun = async ( + db: import("kysely").Kysely, + { + backupId, + filename, + s3Key, + }: { + backupId?: string + filename: string + s3Key: string + } +) => { + if (!backupId) { + const inserted = await db + .insertInto("backup_records") + .values({ filename, s3Key, status: BackupStatus.pending }) + .returning("id") + .executeTakeFirstOrThrow() + + return inserted.id + } + + await db + .updateTable("backup_records") + .set({ filename, s3Key }) + .where("id", "=", backupId as BackupRecordsId) + .execute() + + return backupId +} + +const updateBackupRecord = async ( + db: import("kysely").Kysely, + backupId: string, + update: { + status: BackupStatus + error?: string | null + sizeBytes?: string | null + startedAt?: Date | null + completedAt?: Date | null + } +) => { + await db + .updateTable("backup_records") + .set({ + status: update.status, + ...(update.error !== undefined ? { error: update.error } : {}), + ...(update.sizeBytes !== undefined ? { sizeBytes: update.sizeBytes } : {}), + ...(update.startedAt !== undefined ? { startedAt: update.startedAt } : {}), + ...(update.completedAt !== undefined ? { completedAt: update.completedAt } : {}), + }) + .where("id", "=", backupId as BackupRecordsId) + .execute() +} + +const cleanupExpiredBackups = async ( + db: import("kysely").Kysely, + s3Client: S3Client, + backupS3Config: BackupS3Config, + retentionDays: number +) => { + const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000) + + const expiredBackups = await db + .selectFrom("backup_records") + .select(["id", "s3Key"]) + .where("status", "=", BackupStatus.completed) + .where("completedAt", "is not", null) + .where("completedAt", "<", cutoff) + .execute() + + if (expiredBackups.length === 0) { + return + } + + const objects = expiredBackups.map((backup) => ({ + Key: backup.s3Key, + })) satisfies ObjectIdentifier[] + + await s3Client.send( + new DeleteObjectsCommand({ + Bucket: backupS3Config.bucket, + Delete: { Objects: objects, Quiet: true }, + }) + ) + + await db + .deleteFrom("backup_records") + .where( + "id", + "in", + expiredBackups.map((b) => b.id) + ) + .execute() +} + +const getBackupFileData = (databaseUrl: string, keyPrefix: string) => { + const timestamp = new Date().toISOString().replace(/[:-]/g, "").split(".")[0] + "Z" + const dbName = new URL(databaseUrl).pathname.slice(1) || "appdb" + const filename = `${dbName}-${timestamp}.dump` + const localPath = path.join(tmpdir(), filename) + const normalizedPrefix = keyPrefix.replace(/\/+$/, "") + const s3Key = `${normalizedPrefix}/${filename}` + + return { filename, localPath, s3Key } +} + +const sendFailureNotification = async ( + notificationEmail: string, + errorMessage: string, + filename: string +) => { + const smtpHost = process.env.SMTP_HOST + const smtpPort = process.env.SMTP_PORT + const smtpUser = process.env.SMTP_USERNAME + const smtpPass = process.env.SMTP_PASSWORD + const smtpFrom = process.env.SMTP_FROM + + const isMissingSmtpConfig = !smtpHost || !smtpPort || !smtpUser || !smtpPass || !smtpFrom + if (isMissingSmtpConfig) { + logger.warn({ + msg: "cannot send backup failure notification, missing SMTP configuration", + }) + return + } + + try { + const transporter = nodemailer.createTransport({ + host: smtpHost, + port: parseInt(smtpPort, 10), + auth: { user: smtpUser, pass: smtpPass }, + }) + + await transporter.sendMail({ + from: smtpFrom, + to: notificationEmail, + subject: `Database backup failed: ${filename}`, + text: [ + `A scheduled database backup has failed.`, + ``, + `Filename: ${filename}`, + `Error: ${errorMessage}`, + `Time: ${new Date().toISOString()}`, + ``, + `Check the superadmin dashboard for more details.`, + ].join("\n"), + }) + + logger.info({ msg: "sent backup failure notification email", to: notificationEmail }) + } catch (err) { + logger.warn({ + msg: "error sending backup failure notification email", + error: err instanceof Error ? err.message : String(err), + }) + } +} + +export const createBackup = async (payload: CreateBackupPayload, _helpers: JobHelpers) => { + const databaseUrl = process.env.DATABASE_URL + if (!databaseUrl) { + throw new Error("Missing DATABASE_URL") + } + + const { db, pool } = createBackupDatabase(databaseUrl) + + // for cron-triggered runs (no backupId), check whether we actually need to run + const isScheduledRun = !payload.backupId + if (isScheduledRun) { + const { shouldRun, reason } = await shouldRunScheduledBackup(db) + + if (!shouldRun) { + logger.info({ msg: "skipping scheduled backup", reason }) + await pool.end() + return + } + } + + const backupS3Config = getBackupS3Config() + const sentryEnabled = ensureSentryInitialized() + const s3Client = new S3Client({ + region: backupS3Config.region, + endpoint: backupS3Config.endpoint, + credentials: { + accessKeyId: backupS3Config.accessKey, + secretAccessKey: backupS3Config.secretKey, + }, + forcePathStyle: true, + }) + + const startedAt = new Date() + const { backupId } = payload + const { filename, localPath, s3Key } = getBackupFileData(databaseUrl, backupS3Config.keyPrefix) + const recordId = await upsertBackupRecordForRun(db, { backupId, filename, s3Key }) + + try { + await updateBackupRecord(db, recordId, { + status: BackupStatus.in_progress, + startedAt, + }) + + logger.info({ msg: "starting database backup", backupId: recordId, filename }) + + await execFileAsync("pg_dump", [ + databaseUrl, + "-Fc", + "--no-owner", + "--no-acl", + "-f", + localPath, + ]) + + const fileStats = await stat(localPath) + const upload = new Upload({ + client: s3Client, + params: { + Bucket: backupS3Config.bucket, + Key: s3Key, + Body: createReadStream(localPath), + ContentLength: fileStats.size, + }, + partSize: 64 * 1024 * 1024, + leavePartsOnError: false, + }) + + await upload.done() + + await updateBackupRecord(db, recordId, { + status: BackupStatus.completed, + sizeBytes: String(fileStats.size), + completedAt: new Date(), + }) + + const backupConfig = await getBackupConfig(db) + await cleanupExpiredBackups(db, s3Client, backupS3Config, backupConfig.retentionDays) + + logger.info({ + msg: "database backup completed", + backupId: recordId, + filename, + s3Key, + sizeBytes: fileStats.size, + }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + + await updateBackupRecord(db, recordId, { + status: BackupStatus.failed, + error: errorMessage, + completedAt: new Date(), + }) + + logger.error({ + msg: "database backup failed", + backupId: recordId, + error: errorMessage, + }) + + const backupConfig = await getBackupConfig(db) + if (backupConfig.notificationEmail) { + await sendFailureNotification(backupConfig.notificationEmail, errorMessage, filename) + } + + if (sentryEnabled && error instanceof Error) { + Sentry.captureException(error) + await Sentry.flush(5000) + } + + throw error + } finally { + await unlink(localPath).catch(() => undefined) + s3Client.destroy() + await pool.end() + } +} diff --git a/mock-notify/next.config.ts b/mock-notify/next.config.ts index f172758858..d264af46c1 100644 --- a/mock-notify/next.config.ts +++ b/mock-notify/next.config.ts @@ -1,7 +1,14 @@ import type { NextConfig } from "next" +const basePath = process.env.BASE_PATH || "" + const nextConfig: NextConfig = { reactStrictMode: true, + basePath, + output: "standalone", + env: { + NEXT_PUBLIC_BASE_PATH: basePath, + }, } export default nextConfig diff --git a/mock-notify/src/app/components/HomeClient.tsx b/mock-notify/src/app/components/HomeClient.tsx new file mode 100644 index 0000000000..e1bf4cabff --- /dev/null +++ b/mock-notify/src/app/components/HomeClient.tsx @@ -0,0 +1,179 @@ +"use client" + +import type { PayloadTemplateType } from "~/lib/payloads" +import type { StoredNotification } from "~/lib/store" +import type { AppConfig } from "~/lib/urls" + +import { useCallback, useEffect, useRef, useState } from "react" + +import { apiUrl } from "~/lib/urls" +import { NotificationCard, type ResponsePrefill } from "./NotificationCard" +import { SendNotificationForm } from "./SendNotificationForm" + +export function HomeClient({ config }: { config: AppConfig }) { + const [notifications, setNotifications] = useState([]) + const [filter, setFilter] = useState<"all" | "received" | "sent">("all") + const [isLoading, setIsLoading] = useState(true) + const [prefill, setPrefill] = useState(undefined) + const [formKey, setFormKey] = useState(0) + const formRef = useRef(null) + + const fetchNotifications = useCallback(async () => { + try { + const params = filter !== "all" ? `?direction=${filter}` : "" + const res = await fetch(apiUrl(`/api/notifications${params}`)) + const data = await res.json() + setNotifications(data.notifications) + } catch (error) { + // biome-ignore lint/suspicious/noConsole: shh + console.error("Failed to fetch notifications:", error) + } finally { + setIsLoading(false) + } + }, [filter]) + + useEffect(() => { + void fetchNotifications() + const interval = setInterval(fetchNotifications, 2000) + return () => clearInterval(interval) + }, [fetchNotifications]) + + const handleClearAll = async () => { + if (!confirm("Are you sure you want to clear all notifications?")) return + + await fetch(apiUrl("/api/notifications"), { method: "DELETE" }) + setNotifications([]) + } + + const handleDelete = async (id: string) => { + await fetch(apiUrl(`/api/notifications/${id}`), { method: "DELETE" }) + setNotifications((prev) => prev.filter((n) => n.id !== id)) + } + + const handleRespond = (_responseType: PayloadTemplateType, newPrefill: ResponsePrefill) => { + setPrefill(newPrefill) + setFormKey((k) => k + 1) + formRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }) + } + + const handleSent = () => { + setPrefill(undefined) + void fetchNotifications() + } + + const receivedCount = notifications.filter((n) => n.direction === "received").length + const sentCount = notifications.filter((n) => n.direction === "sent").length + + return ( +
+
+
+ {/* Send Notification Form */} +
+ + {prefill && ( + + )} +
+ + {/* Notifications List */} +
+
+ {/* Header with filters */} +
+
+

Notifications

+
+ + + +
+
+ + {notifications.length > 0 && ( + + )} +
+ + {/* Notifications */} +
+ {isLoading ? ( +
+ Loading notifications... +
+ ) : notifications.length === 0 ? ( +
+ No notifications yet. Send one or wait for incoming + requests. +
+ ) : ( + notifications.map((notification, index) => ( + handleDelete(notification.id)} + onRespond={handleRespond} + /> + )) + )} +
+
+
+
+
+
+ ) +} diff --git a/mock-notify/src/app/components/NotificationCard.tsx b/mock-notify/src/app/components/NotificationCard.tsx index 2dff1e6979..a9221c179e 100644 --- a/mock-notify/src/app/components/NotificationCard.tsx +++ b/mock-notify/src/app/components/NotificationCard.tsx @@ -1,6 +1,7 @@ "use client" import type { StoredNotification } from "~/lib/store" +import type { AppConfig } from "~/lib/urls" import { useState } from "react" @@ -11,6 +12,7 @@ import { } from "~/lib/payloads" interface NotificationCardProps { + config: AppConfig notification: StoredNotification notifications: StoredNotification[] onDelete: () => void @@ -28,6 +30,7 @@ export interface ResponsePrefill { } export function NotificationCard({ + config, notification, notifications, onDelete, @@ -134,7 +137,7 @@ export function NotificationCard({ templateType: responseType, inReplyTo: payload.inReplyTo ?? payload.id, inReplyToObjectUrl, - originUrl: payload.origin?.id ?? "http://localhost:4001", + originUrl: payload.origin?.id ?? config.selfUrl, targetServiceUrl: payload.target?.id ?? "", } onRespond(responseType, prefill) @@ -146,7 +149,7 @@ export function NotificationCard({ templateType: responseType, inReplyTo: payload.id, inReplyToObjectUrl: payload.object?.id ?? "", - originUrl: "http://localhost:4001", + originUrl: config.selfUrl, targetServiceUrl: payload.origin?.id ?? "", } onRespond(responseType, prefill) diff --git a/mock-notify/src/app/components/SendNotificationForm.tsx b/mock-notify/src/app/components/SendNotificationForm.tsx index 58639f0a7f..69f357a878 100644 --- a/mock-notify/src/app/components/SendNotificationForm.tsx +++ b/mock-notify/src/app/components/SendNotificationForm.tsx @@ -1,6 +1,7 @@ "use client" import type { CoarNotifyPayload } from "~/lib/store" +import type { AppConfig } from "~/lib/urls" import { useState } from "react" @@ -13,10 +14,11 @@ import { createRejectPayload, type PayloadTemplateType, } from "~/lib/payloads" +import { apiUrl } from "~/lib/urls" interface SendNotificationFormProps { + config: AppConfig onSent: () => void - /** Pre-fill values for a response to an existing notification */ prefill?: { targetUrl?: string templateType?: PayloadTemplateType @@ -38,73 +40,74 @@ const TEMPLATE_OPTIONS: PayloadTemplateType[] = [ "Reject", ] -export function SendNotificationForm({ onSent, prefill }: SendNotificationFormProps) { +function makeTemplateDefaults( + pubpubUrl: string +): Record { + return { + "Offer Review": { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us2-unjournal/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us2-unjournal`, + }, + "Announce Review": { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us1-arcadia`, + }, + "Offer Ingest": { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us4-repository/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us4-repository`, + }, + "Announce Ingest": { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us4-repository/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us4-repository`, + }, + Accept: { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us1-arcadia`, + }, + Reject: { + targetUrl: `${pubpubUrl}/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox`, + targetServiceUrl: `${pubpubUrl}/c/coar-us1-arcadia`, + }, + } +} + +export function SendNotificationForm({ config, onSent, prefill }: SendNotificationFormProps) { + const { pubpubUrl, selfUrl } = config + const templateDefaults = makeTemplateDefaults(pubpubUrl) + const [mode, setMode] = useState("template") + const [targetUrl, setTargetUrl] = useState( - prefill?.targetUrl ?? - "http://localhost:3000/api/v0/c/coar-us2-unjournal/site/webhook/coar-inbox" + prefill?.targetUrl ?? templateDefaults["Offer Review"].targetUrl ) + const [templateType, setTemplateType] = useState( prefill?.templateType ?? "Offer Review" ) + const [customPayload, setCustomPayload] = useState("") const [isSending, setIsSending] = useState(false) const [error, setError] = useState(null) const [success, setSuccess] = useState(false) - // Template fields - using complete URLs instead of IDs const [objectUrl, setObjectUrl] = useState( prefill?.inReplyToObjectUrl ?? "https://www.biorxiv.org/content/10.1101/2024.01.01.123456" ) const [objectCiteAs, setObjectCiteAs] = useState("") const [objectItemUrl, setObjectItemUrl] = useState("") - const [reviewUrl, setReviewUrl] = useState("http://localhost:4001/reviews/sample-review") - const [originUrl, setOriginUrl] = useState(prefill?.originUrl ?? "http://localhost:4001") - const [targetServiceUrl, setTargetServiceUrl] = useState( - prefill?.targetServiceUrl ?? "http://localhost:3000" - ) + const [reviewUrl, setReviewUrl] = useState(`${selfUrl}/reviews/sample-review`) + const [originUrl, setOriginUrl] = useState(prefill?.originUrl ?? selfUrl) + const [targetServiceUrl, setTargetServiceUrl] = useState(prefill?.targetServiceUrl ?? pubpubUrl) const [serviceName, setServiceName] = useState("Mock Review Service") const [inReplyTo, setInReplyTo] = useState(prefill?.inReplyTo ?? "") const [inReplyToUrl, setInReplyToUrl] = useState(prefill?.inReplyToObjectUrl ?? "") - const [workUrl, setWorkUrl] = useState( - "http://localhost:3000/c/coar-us4-repository/pub/{pubId}" - ) - - // Default target URLs per template type for demo convenience - const TEMPLATE_DEFAULTS: Record< - PayloadTemplateType, - { targetUrl: string; targetServiceUrl: string } - > = { - "Offer Review": { - targetUrl: "http://localhost:3000/api/v0/c/coar-us2-unjournal/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us2-unjournal", - }, - "Announce Review": { - targetUrl: "http://localhost:3000/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us1-arcadia", - }, - "Offer Ingest": { - targetUrl: "http://localhost:3000/api/v0/c/coar-us4-repository/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us4-repository", - }, - "Announce Ingest": { - targetUrl: "http://localhost:3000/api/v0/c/coar-us4-repository/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us4-repository", - }, - Accept: { - targetUrl: "http://localhost:3000/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us1-arcadia", - }, - Reject: { - targetUrl: "http://localhost:3000/api/v0/c/coar-us1-arcadia/site/webhook/coar-inbox", - targetServiceUrl: "http://localhost:3000/c/coar-us1-arcadia", - }, - } + const [workUrl, setWorkUrl] = useState(`${pubpubUrl}/c/coar-us4-repository/pub/{pubId}`) const handleTemplateChange = (newType: PayloadTemplateType) => { setTemplateType(newType) + if (!prefill) { - const defaults = TEMPLATE_DEFAULTS[newType] + const defaults = templateDefaults[newType] setTargetUrl(defaults.targetUrl) setTargetServiceUrl(defaults.targetServiceUrl) } @@ -165,13 +168,14 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr try { let payload: CoarNotifyPayload + if (mode === "template") { payload = generatePayload() } else { payload = JSON.parse(customPayload) } - const res = await fetch("/api/send", { + const res = await fetch(apiUrl("/api/send"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ targetUrl, payload }), @@ -278,7 +282,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr type="text" value={reviewUrl} onChange={(e) => setReviewUrl(e.target.value)} - placeholder="http://localhost:4000/review/..." className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -290,7 +293,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr type="text" value={inReplyToUrl} onChange={(e) => setInReplyToUrl(e.target.value)} - placeholder="http://localhost:3000/c/community/pub/..." className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -344,7 +346,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr type="text" value={reviewUrl} onChange={(e) => setReviewUrl(e.target.value)} - placeholder="http://localhost:4001/review/..." className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -386,7 +387,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr type="text" value={reviewUrl} onChange={(e) => setReviewUrl(e.target.value)} - placeholder="http://localhost:4001/review/..." className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -398,7 +398,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr type="text" value={workUrl} onChange={(e) => setWorkUrl(e.target.value)} - placeholder="http://localhost:3000/c/coar-us4-repository/pub/{pubId}" className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -533,7 +532,6 @@ export function SendNotificationForm({ onSent, prefill }: SendNotificationFormPr value={targetUrl} onChange={(e) => setTargetUrl(e.target.value)} className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" - placeholder="http://localhost:3000/api/v0/c/..." /> diff --git a/mock-notify/src/app/page.tsx b/mock-notify/src/app/page.tsx index e0fa5fbf99..013d70ab10 100644 --- a/mock-notify/src/app/page.tsx +++ b/mock-notify/src/app/page.tsx @@ -1,172 +1,19 @@ -"use client" +import type { AppConfig } from "~/lib/urls" -import type { PayloadTemplateType } from "~/lib/payloads" -import type { StoredNotification } from "~/lib/store" +import { HomeClient } from "./components/HomeClient" -import { useCallback, useEffect, useRef, useState } from "react" +const basePath = process.env.BASE_PATH || "" -import { NotificationCard, type ResponsePrefill } from "./components/NotificationCard" -import { SendNotificationForm } from "./components/SendNotificationForm" +export const dynamic = "force-dynamic" -export default function Home() { - const [notifications, setNotifications] = useState([]) - const [filter, setFilter] = useState<"all" | "received" | "sent">("all") - const [isLoading, setIsLoading] = useState(true) - const [prefill, setPrefill] = useState(undefined) - const [formKey, setFormKey] = useState(0) - const formRef = useRef(null) +export default function Page() { + const pubpubUrl = process.env.PUBSTAR_URL || "http://localhost:3000" - const fetchNotifications = useCallback(async () => { - try { - const params = filter !== "all" ? `?direction=${filter}` : "" - const res = await fetch(`/api/notifications${params}`) - const data = await res.json() - setNotifications(data.notifications) - } catch (error) { - // biome-ignore lint/suspicious/noConsole: shh - console.error("Failed to fetch notifications:", error) - } finally { - setIsLoading(false) - } - }, [filter]) + const selfUrl = basePath + ? `${pubpubUrl}${basePath}` + : `http://localhost:${process.env.PORT || "4001"}` - useEffect(() => { - void fetchNotifications() - // Poll for new notifications every 2 seconds - const interval = setInterval(fetchNotifications, 2000) - return () => clearInterval(interval) - }, [fetchNotifications]) + const config: AppConfig = { pubpubUrl, selfUrl } - const handleClearAll = async () => { - if (!confirm("Are you sure you want to clear all notifications?")) return - await fetch("/api/notifications", { method: "DELETE" }) - setNotifications([]) - } - - const handleDelete = async (id: string) => { - await fetch(`/api/notifications/${id}`, { method: "DELETE" }) - setNotifications((prev) => prev.filter((n) => n.id !== id)) - } - - const handleRespond = (_responseType: PayloadTemplateType, newPrefill: ResponsePrefill) => { - setPrefill(newPrefill) - // Force form to re-mount with new prefill values - setFormKey((k) => k + 1) - // Scroll to form - formRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }) - } - - const handleSent = () => { - setPrefill(undefined) - void fetchNotifications() - } - - const receivedCount = notifications.filter((n) => n.direction === "received").length - const sentCount = notifications.filter((n) => n.direction === "sent").length - - return ( -
-
-
- {/* Send Notification Form */} -
- - {prefill && ( - - )} -
- - {/* Notifications List */} -
-
- {/* Header with filters */} -
-
-

Notifications

-
- - - -
-
- {notifications.length > 0 && ( - - )} -
- - {/* Notifications */} -
- {isLoading ? ( -
- Loading notifications... -
- ) : notifications.length === 0 ? ( -
- No notifications yet. Send one or wait for incoming - requests. -
- ) : ( - notifications.map((notification, index) => ( - handleDelete(notification.id)} - onRespond={handleRespond} - /> - )) - )} -
-
-
-
-
-
- ) + return } diff --git a/mock-notify/src/app/reviews/sample-review/docmap/route.ts b/mock-notify/src/app/reviews/sample-review/docmap/route.ts index c815b1e2fe..3cbdfddeaf 100644 --- a/mock-notify/src/app/reviews/sample-review/docmap/route.ts +++ b/mock-notify/src/app/reviews/sample-review/docmap/route.ts @@ -1,5 +1,8 @@ -const MOCK_BASE = "http://localhost:4001" -const REVIEW_URL = `${MOCK_BASE}/reviews/sample-review` +const basePath = process.env.BASE_PATH || "" +const pubpubUrl = process.env.PUBSTAR_URL || "http://localhost:4001" + +const selfUrl = basePath ? `${pubpubUrl}${basePath}` : pubpubUrl +const REVIEW_URL = `${selfUrl}/reviews/sample-review` const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", diff --git a/mock-notify/src/app/reviews/sample-review/route.ts b/mock-notify/src/app/reviews/sample-review/route.ts index 196862802b..9af344f5ca 100644 --- a/mock-notify/src/app/reviews/sample-review/route.ts +++ b/mock-notify/src/app/reviews/sample-review/route.ts @@ -1,5 +1,8 @@ -const MOCK_BASE = "http://localhost:4001" -const REVIEW_URL = `${MOCK_BASE}/reviews/sample-review` +const basePath = process.env.BASE_PATH || "" +const pubpubUrl = process.env.PUBSTAR_URL || "http://localhost:4001" + +const selfUrl = basePath ? `${pubpubUrl}${basePath}` : pubpubUrl +const REVIEW_URL = `${selfUrl}/reviews/sample-review` const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", diff --git a/mock-notify/src/lib/urls.ts b/mock-notify/src/lib/urls.ts new file mode 100644 index 0000000000..0d44fee584 --- /dev/null +++ b/mock-notify/src/lib/urls.ts @@ -0,0 +1,9 @@ +const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "" + +/** prefix a path with the app's basePath for client-side fetch calls */ +export const apiUrl = (path: string) => `${basePath}${path}` + +export interface AppConfig { + pubpubUrl: string + selfUrl: string +} diff --git a/package.json b/package.json index 58a7c2e858..84f24cfdaa 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,13 @@ "dev:teardown": "docker compose -f docker-compose.dev.yml down -v", "integration:setup": "docker compose -f docker-compose.test.yml --profile integration up -d", "integration:teardown": "docker compose -f docker-compose.test.yml --profile integration down -v", - "context-editor:playwright": "pnpm --filter context-editor run playwright:test" + "context-editor:playwright": "pnpm --filter context-editor run playwright:test", + "secrets:encrypt": "cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.enc .env", + "secrets:encrypt:preview": "cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.preview.enc .env.preview", + "secrets:encrypt:sandbox": "cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.sandbox.enc .env.sandbox", + "secrets:decrypt": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env .env.enc", + "secrets:decrypt:preview": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env.preview .env.preview.enc", + "secrets:decrypt:sandbox": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env.sandbox .env.sandbox.enc" }, "devDependencies": { "@babel/core": "7.28.3", diff --git a/packages/context-editor/.env.development b/packages/context-editor/.env.development index 6aebb56c76..c74ce465e7 100644 --- a/packages/context-editor/.env.development +++ b/packages/context-editor/.env.development @@ -5,4 +5,4 @@ STORYBOOK_ASSETS_REGION="us-east-1" STORYBOOK_ASSETS_UPLOAD_KEY="pubpubuser" STORYBOOK_ASSETS_UPLOAD_SECRET_KEY="pubpubpass" STORYBOOK_ASSETS_STORAGE_ENDPOINT="http://localhost:9000" -STORYBOOK_ASSETS_BUCKET_NAME="assets.v7.pubpub.org" \ No newline at end of file +STORYBOOK_ASSETS_BUCKET_NAME="assets.pubpub.org" \ No newline at end of file diff --git a/packages/context-editor/.env.test b/packages/context-editor/.env.test index 8f983370b3..ea4d558528 100644 --- a/packages/context-editor/.env.test +++ b/packages/context-editor/.env.test @@ -6,4 +6,4 @@ STORYBOOK_ASSETS_REGION="us-east-1" STORYBOOK_ASSETS_UPLOAD_KEY="pubpubuser" STORYBOOK_ASSETS_UPLOAD_SECRET_KEY="pubpubpass" STORYBOOK_ASSETS_STORAGE_ENDPOINT="http://localhost:9000" -STORYBOOK_ASSETS_BUCKET_NAME="assets.v7.pubpub.org" \ No newline at end of file +STORYBOOK_ASSETS_BUCKET_NAME="assets.pubpub.org" \ No newline at end of file diff --git a/packages/context-editor/src/ContextEditor.tsx b/packages/context-editor/src/ContextEditor.tsx index 3e548af6ac..74ad4ccf14 100644 --- a/packages/context-editor/src/ContextEditor.tsx +++ b/packages/context-editor/src/ContextEditor.tsx @@ -12,6 +12,7 @@ import "katex/dist/katex.min.css" import type { Node } from "prosemirror-model" import type { ForwardRefExoticComponent, RefAttributes, RefObject } from "react" +import type { FileUploadProps } from "ui/customRenderers/fileUpload/fileUpload" import { useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react" import { ProseMirror, ProseMirrorDoc, reactKeys } from "@handlewithcare/react-prosemirror" @@ -53,7 +54,7 @@ export interface ContextEditorProps { NodeViewComponentProps & RefAttributes > /* A react component that is given the ContextAtom pubtype and renders it accordingly */ hideMenu?: boolean - upload: (fileName: string) => Promise + upload: FileUploadProps["upload"] /** * Ref to the context editor getter diff --git a/packages/context-editor/src/schemas/contextDoc.ts b/packages/context-editor/src/schemas/contextDoc.ts index 37a0669bfa..dacf4b1a19 100644 --- a/packages/context-editor/src/schemas/contextDoc.ts +++ b/packages/context-editor/src/schemas/contextDoc.ts @@ -55,7 +55,7 @@ continual linking. I propose we lean away from that, into a more explicit 'include' model. More like a package-lock file than a dynamic transclusion model. -Should this use @pubpub/prosemirror-reactive? +Should this use @pubstar/prosemirror-reactive? It seems like the core function is similar, we want to include an up-to-date value of a field based on some stored permanent id. Maybe we don't? Maybe we want to snapshot the diff --git a/packages/context-editor/src/stories/MediaUpload.stories.tsx b/packages/context-editor/src/stories/MediaUpload.stories.tsx index f0b0cc7844..0d755d6309 100644 --- a/packages/context-editor/src/stories/MediaUpload.stories.tsx +++ b/packages/context-editor/src/stories/MediaUpload.stories.tsx @@ -26,7 +26,7 @@ const attrs = { id: "", class: null, alt: "cat.jpeg", - src: "http://localhost:9000/assets.v7.pubpub.org/a85b4157-4a7f-40d8-bb40-d9c17a6c7a70/cat.jpeg", + src: "http://localhost:9000/assets.pubpub.org/a85b4157-4a7f-40d8-bb40-d9c17a6c7a70/cat.jpeg", linkTo: "", width: 100, align: "center", diff --git a/packages/context-editor/src/stories/mockUtils.ts b/packages/context-editor/src/stories/mockUtils.ts index c4415b9f73..c966cebd02 100644 --- a/packages/context-editor/src/stories/mockUtils.ts +++ b/packages/context-editor/src/stories/mockUtils.ts @@ -20,18 +20,18 @@ export const getPubs = async (filter: string) => { * Requires minio server to be running * */ const getS3Client = () => { - const region = import.meta.env.STORYBOOK_ASSETS_REGION - const key = import.meta.env.STORYBOOK_ASSETS_UPLOAD_KEY - const secret = import.meta.env.STORYBOOK_ASSETS_UPLOAD_SECRET_KEY + const region = import.meta.env.STORYBOOK_S3_REGION + const key = import.meta.env.STORYBOOK_S3_ACCESS_KEY + const secret = import.meta.env.STORYBOOK_S3_SECRET_KEY const s3Client = new S3Client({ - endpoint: import.meta.env.STORYBOOK_ASSETS_STORAGE_ENDPOINT, + endpoint: import.meta.env.STORYBOOK_S3_ENDPOINT, region: region, credentials: { accessKeyId: key, secretAccessKey: secret, }, - forcePathStyle: import.meta.env.STORYBOOK_ASSETS_STORAGE_ENDPOINT, // Required for MinIO + forcePathStyle: import.meta.env.STORYBOOK_S3_ENDPOINT, // Required for MinIO }) return s3Client @@ -40,7 +40,7 @@ const getS3Client = () => { export const generateSignedAssetUploadUrl = async (key: string) => { const client = getS3Client() - const bucket = import.meta.env.STORYBOOK_ASSETS_BUCKET_NAME + const bucket = import.meta.env.STORYBOOK_S3_BUCKET_NAME const command = new PutObjectCommand({ Bucket: bucket, Key: key, diff --git a/packages/db/src/public.ts b/packages/db/src/public.ts index d88f04c7c5..f8dc726786 100644 --- a/packages/db/src/public.ts +++ b/packages/db/src/public.ts @@ -19,6 +19,9 @@ export * from "./public/AutomationRun" export * from "./public/AutomationRuns" export * from "./public/Automations" export * from "./public/AutomationTriggers" +export * from "./public/BackupConfig" +export * from "./public/BackupRecords" +export * from "./public/BackupStatus" export * from "./public/Capabilities" export * from "./public/Communities" export * from "./public/CommunityMemberships" diff --git a/packages/db/src/public/Action.ts b/packages/db/src/public/Action.ts index 0514246971..56811e4232 100644 --- a/packages/db/src/public/Action.ts +++ b/packages/db/src/public/Action.ts @@ -11,8 +11,8 @@ export enum Action { move = "move", googleDriveImport = "googleDriveImport", datacite = "datacite", - createPub = "createPub", buildSite = "buildSite", + createPub = "createPub", } /** Zod schema for Action */ diff --git a/packages/db/src/public/BackupConfig.ts b/packages/db/src/public/BackupConfig.ts new file mode 100644 index 0000000000..3657f81027 --- /dev/null +++ b/packages/db/src/public/BackupConfig.ts @@ -0,0 +1,64 @@ +// @generated +// This file is automatically generated by Kanel. Do not modify manually. + +import type { ColumnType, Insertable, Selectable, Updateable } from "kysely" + +import { z } from "zod" + +/** Identifier type for public.backup_config */ +export type BackupConfigId = string & { __brand: "BackupConfigId" } + +/** Represents the table public.backup_config */ +export interface BackupConfigTable { + id: ColumnType + + enabled: ColumnType + + intervalHours: ColumnType + + retentionDays: ColumnType + + notificationEmail: ColumnType + + createdAt: ColumnType + + updatedAt: ColumnType +} + +export type BackupConfig = Selectable + +export type NewBackupConfig = Insertable + +export type BackupConfigUpdate = Updateable + +export const backupConfigIdSchema = z.string().uuid() as unknown as z.Schema + +export const backupConfigSchema = z.object({ + id: backupConfigIdSchema, + enabled: z.boolean(), + intervalHours: z.number(), + retentionDays: z.number(), + notificationEmail: z.string().nullable(), + createdAt: z.date(), + updatedAt: z.date(), +}) + +export const backupConfigInitializerSchema = z.object({ + id: backupConfigIdSchema.optional(), + enabled: z.boolean().optional(), + intervalHours: z.number().optional(), + retentionDays: z.number().optional(), + notificationEmail: z.string().optional().nullable(), + createdAt: z.date().optional(), + updatedAt: z.date().optional(), +}) + +export const backupConfigMutatorSchema = z.object({ + id: backupConfigIdSchema.optional(), + enabled: z.boolean().optional(), + intervalHours: z.number().optional(), + retentionDays: z.number().optional(), + notificationEmail: z.string().optional().nullable(), + createdAt: z.date().optional(), + updatedAt: z.date().optional(), +}) diff --git a/packages/db/src/public/BackupRecords.ts b/packages/db/src/public/BackupRecords.ts new file mode 100644 index 0000000000..91ed692982 --- /dev/null +++ b/packages/db/src/public/BackupRecords.ts @@ -0,0 +1,81 @@ +// @generated +// This file is automatically generated by Kanel. Do not modify manually. + +import type { ColumnType, Insertable, Selectable, Updateable } from "kysely" + +import { z } from "zod" + +import { type BackupStatus, backupStatusSchema } from "./BackupStatus" + +/** Identifier type for public.backup_records */ +export type BackupRecordsId = string & { __brand: "BackupRecordsId" } + +/** Represents the table public.backup_records */ +export interface BackupRecordsTable { + id: ColumnType + + filename: ColumnType + + s3Key: ColumnType + + sizeBytes: ColumnType + + status: ColumnType + + error: ColumnType + + startedAt: ColumnType + + completedAt: ColumnType + + createdAt: ColumnType + + updatedAt: ColumnType +} + +export type BackupRecords = Selectable + +export type NewBackupRecords = Insertable + +export type BackupRecordsUpdate = Updateable + +export const backupRecordsIdSchema = z.string().uuid() as unknown as z.Schema + +export const backupRecordsSchema = z.object({ + id: backupRecordsIdSchema, + filename: z.string(), + s3Key: z.string(), + sizeBytes: z.string().nullable(), + status: backupStatusSchema, + error: z.string().nullable(), + startedAt: z.date().nullable(), + completedAt: z.date().nullable(), + createdAt: z.date(), + updatedAt: z.date(), +}) + +export const backupRecordsInitializerSchema = z.object({ + id: backupRecordsIdSchema.optional(), + filename: z.string(), + s3Key: z.string(), + sizeBytes: z.string().optional().nullable(), + status: backupStatusSchema.optional(), + error: z.string().optional().nullable(), + startedAt: z.date().optional().nullable(), + completedAt: z.date().optional().nullable(), + createdAt: z.date().optional(), + updatedAt: z.date().optional(), +}) + +export const backupRecordsMutatorSchema = z.object({ + id: backupRecordsIdSchema.optional(), + filename: z.string().optional(), + s3Key: z.string().optional(), + sizeBytes: z.string().optional().nullable(), + status: backupStatusSchema.optional(), + error: z.string().optional().nullable(), + startedAt: z.date().optional().nullable(), + completedAt: z.date().optional().nullable(), + createdAt: z.date().optional(), + updatedAt: z.date().optional(), +}) diff --git a/packages/db/src/public/BackupStatus.ts b/packages/db/src/public/BackupStatus.ts new file mode 100644 index 0000000000..746bf81cc4 --- /dev/null +++ b/packages/db/src/public/BackupStatus.ts @@ -0,0 +1,15 @@ +// @generated +// This file is automatically generated by Kanel. Do not modify manually. + +import { z } from "zod" + +/** Represents the enum public.BackupStatus */ +export enum BackupStatus { + pending = "pending", + in_progress = "in_progress", + completed = "completed", + failed = "failed", +} + +/** Zod schema for BackupStatus */ +export const backupStatusSchema = z.nativeEnum(BackupStatus) diff --git a/packages/db/src/public/PublicSchema.ts b/packages/db/src/public/PublicSchema.ts index 416c19c598..77a47547af 100644 --- a/packages/db/src/public/PublicSchema.ts +++ b/packages/db/src/public/PublicSchema.ts @@ -13,6 +13,8 @@ import type { AutomationConditionsTable } from "./AutomationConditions" import type { AutomationRunsTable } from "./AutomationRuns" import type { AutomationsTable } from "./Automations" import type { AutomationTriggersTable } from "./AutomationTriggers" +import type { BackupConfigTable } from "./BackupConfig" +import type { BackupRecordsTable } from "./BackupRecords" import type { CommunitiesTable } from "./Communities" import type { CommunityMembershipsTable } from "./CommunityMemberships" import type { FormElementsTable } from "./FormElements" @@ -43,77 +45,81 @@ import type { UsersTable } from "./Users" export interface PublicSchema { PubFieldSchema: PubFieldSchemaTable - automation_runs: AutomationRunsTable + PubsInStages: PubsInStagesTable - membership_capabilities: MembershipCapabilitiesTable + _FormElementToPubType: FormElementToPubTypeTable - sessions: SessionsTable + _MemberGroupToUser: MemberGroupToUserTable - move_constraint: MoveConstraintTable + _PubFieldToPubType: PubFieldToPubTypeTable - pub_values_history: PubValuesHistoryTable + _prisma_migrations: PrismaMigrationsTable - api_access_tokens: ApiAccessTokensTable + action_config_defaults: ActionConfigDefaultsTable - _MemberGroupToUser: MemberGroupToUserTable + action_instances: ActionInstancesTable - _FormElementToPubType: FormElementToPubTypeTable + action_runs: ActionRunsTable - PubsInStages: PubsInStagesTable + api_access_logs: ApiAccessLogsTable - stage_memberships: StageMembershipsTable + api_access_permissions: ApiAccessPermissionsTable - _prisma_migrations: PrismaMigrationsTable + api_access_tokens: ApiAccessTokensTable auth_tokens: AuthTokensTable - api_access_permissions: ApiAccessPermissionsTable + automation_condition_blocks: AutomationConditionBlocksTable - api_access_logs: ApiAccessLogsTable + automation_conditions: AutomationConditionsTable - pub_types: PubTypesTable + automation_runs: AutomationRunsTable - automation_conditions: AutomationConditionsTable + automation_triggers: AutomationTriggersTable - pub_fields: PubFieldsTable + automations: AutomationsTable - invites_history: InvitesHistoryTable + backup_config: BackupConfigTable - communities: CommunitiesTable + backup_records: BackupRecordsTable - automation_triggers: AutomationTriggersTable + communities: CommunitiesTable - users: UsersTable + community_memberships: CommunityMembershipsTable - _PubFieldToPubType: PubFieldToPubTypeTable + form_elements: FormElementsTable - pub_values: PubValuesTable + forms: FormsTable - action_runs: ActionRunsTable + invite_forms: InviteFormsTable invites: InvitesTable - pubs: PubsTable + invites_history: InvitesHistoryTable - action_config_defaults: ActionConfigDefaultsTable + member_groups: MemberGroupsTable - action_instances: ActionInstancesTable + membership_capabilities: MembershipCapabilitiesTable - community_memberships: CommunityMembershipsTable + move_constraint: MoveConstraintTable - member_groups: MemberGroupsTable + pub_fields: PubFieldsTable pub_memberships: PubMembershipsTable - stages: StagesTable + pub_types: PubTypesTable - automation_condition_blocks: AutomationConditionBlocksTable + pub_values: PubValuesTable - form_elements: FormElementsTable + pub_values_history: PubValuesHistoryTable - forms: FormsTable + pubs: PubsTable - automations: AutomationsTable + sessions: SessionsTable - invite_forms: InviteFormsTable + stage_memberships: StageMembershipsTable + + stages: StagesTable + + users: UsersTable } diff --git a/packages/db/src/table-names.ts b/packages/db/src/table-names.ts index 7dee881561..b4a2523255 100644 --- a/packages/db/src/table-names.ts +++ b/packages/db/src/table-names.ts @@ -20,6 +20,8 @@ export const databaseTableNames = [ "automation_runs", "automation_triggers", "automations", + "backup_config", + "backup_records", "communities", "community_memberships", "form_elements", @@ -1173,6 +1175,148 @@ export const databaseTables = [ }, ], }, + { + name: "backup_config", + isView: false, + schema: "public", + columns: [ + { + name: "id", + dataType: "text", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "enabled", + dataType: "bool", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "intervalHours", + dataType: "int4", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "retentionDays", + dataType: "int4", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "createdAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "updatedAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + ], + }, + { + name: "backup_records", + isView: false, + schema: "public", + columns: [ + { + name: "id", + dataType: "text", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "filename", + dataType: "text", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "s3Key", + dataType: "text", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "sizeBytes", + dataType: "int8", + dataTypeSchema: "pg_catalog", + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "status", + dataType: "BackupStatus", + dataTypeSchema: "public", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "error", + dataType: "text", + dataTypeSchema: "pg_catalog", + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "startedAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "completedAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "createdAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + { + name: "updatedAt", + dataType: "timestamp", + dataTypeSchema: "pg_catalog", + isNullable: false, + isAutoIncrementing: false, + hasDefaultValue: true, + }, + ], + }, { name: "communities", isView: false, diff --git a/packages/emails/.env.development b/packages/emails/.env.development index 9de4176119..101030a7cf 100644 --- a/packages/emails/.env.development +++ b/packages/emails/.env.development @@ -1 +1 @@ -PUBPUB_URL=http://localhost:3000 +PUBSTAR_HOSTNAME=http://localhost:3000 diff --git a/packages/emails/src/form-link.tsx b/packages/emails/src/form-link.tsx index b28c5ffe2d..a95a621f33 100644 --- a/packages/emails/src/form-link.tsx +++ b/packages/emails/src/form-link.tsx @@ -35,7 +35,7 @@ export const FormLink = ({ form, previewText = `Requesting access to ${form.name}`, }: RequestAccessToForm) => { - const baseUrl = process.env.PUBPUB_URL ?? "" + const baseUrl = process.env.PUBSTAR_URL ?? "" return ( diff --git a/packages/emails/src/password-reset.tsx b/packages/emails/src/password-reset.tsx index 48a1e878bd..df398d95e2 100644 --- a/packages/emails/src/password-reset.tsx +++ b/packages/emails/src/password-reset.tsx @@ -28,7 +28,7 @@ export const PasswordReset = ({ resetPasswordLink, previewText = `Reset your PubPub password`, }: ForgotPasswordProps) => { - const baseUrl = process.env.PUBPUB_URL ? process.env.PUBPUB_URL : "" + const baseUrl = process.env.PUBSTAR_URL ? process.env.PUBSTAR_URL : "" return ( diff --git a/packages/emails/src/signup-invite.tsx b/packages/emails/src/signup-invite.tsx index 179e197a7b..b611ccef31 100644 --- a/packages/emails/src/signup-invite.tsx +++ b/packages/emails/src/signup-invite.tsx @@ -91,7 +91,7 @@ const defaultPreviewText = (props: SignupInviteProps) => { } export const Invite = (props: SignupInviteProps) => { - const baseUrl = process.env.PUBPUB_URL ?? "" + const baseUrl = process.env.PUBSTAR_URL ?? "" const community = props.community ?? { name: "CrocCroc", diff --git a/packages/emails/src/verify-email.tsx b/packages/emails/src/verify-email.tsx index 69d03d5166..4baa63116e 100644 --- a/packages/emails/src/verify-email.tsx +++ b/packages/emails/src/verify-email.tsx @@ -26,7 +26,7 @@ export const VerifyEmail = ({ verifyEmailLink, previewText = `Verify your email`, }: VerifyEmailprops) => { - const baseUrl = process.env.PUBPUB_URL ? process.env.PUBPUB_URL : "" + const baseUrl = process.env.PUBSTAR_URL ? process.env.PUBSTAR_URL : "" return ( diff --git a/packages/json-interpolate/package.json b/packages/json-interpolate/package.json index 8986b0c78f..f31fa38224 100644 --- a/packages/json-interpolate/package.json +++ b/packages/json-interpolate/package.json @@ -1,9 +1,9 @@ { - "name": "@pubpub/json-interpolate", + "name": "@pubstar/json-interpolate", "type": "module", "version": "0.0.1", "exports": { - ".": "./dist/pubpub-json-interpolate.js", + ".": "./dist/pubstar-json-interpolate.js", "./package.json": "./package.json" }, "scripts": { diff --git a/packages/ui/src/customRenderers/fileUpload/fileUpload.tsx b/packages/ui/src/customRenderers/fileUpload/fileUpload.tsx index e574f9dbbd..6466b8b4ca 100644 --- a/packages/ui/src/customRenderers/fileUpload/fileUpload.tsx +++ b/packages/ui/src/customRenderers/fileUpload/fileUpload.tsx @@ -18,6 +18,13 @@ import AwsS3Multipart from "@uppy/aws-s3" const pluginName = "AwsS3Multipart" as const +export type SignedUploadTarget = { + signedUrl: string + publicUrl: string +} + +export type UploadResponse = string | SignedUploadTarget | { error: string } + export type FormattedFile = { id: string fileName: string @@ -30,7 +37,7 @@ export type FormattedFile = { } export type FileUploadProps = { - upload: (fileName: string) => Promise + upload: (fileName: string) => Promise onUpdateFiles: (files: FormattedFile[]) => void disabled?: boolean id?: string @@ -47,6 +54,11 @@ const FileUpload = forwardRef(function FileUpload(props: FileUploadProps, _ref) const handler = () => { const uploadedFiles = uppy.getFiles() const formattedFiles = uploadedFiles.map((file) => { + const publicUploadUrl = + typeof file.meta.publicUploadUrl === "string" + ? file.meta.publicUploadUrl + : undefined + return { id: file.id, fileName: file.name, @@ -54,7 +66,7 @@ const FileUpload = forwardRef(function FileUpload(props: FileUploadProps, _ref) fileType: file.type, fileSize: file.size, fileMeta: file.meta, - fileUploadUrl: file.response?.uploadURL, + fileUploadUrl: publicUploadUrl ?? file.response?.uploadURL, filePreview: file.preview, } }) as FormattedFile[] @@ -80,22 +92,33 @@ const FileUpload = forwardRef(function FileUpload(props: FileUploadProps, _ref) throw new Error("File name is required") } - const signedUrl = await props.upload(file.name) + const uploadResponse = await props.upload(file.name) + + if (typeof uploadResponse === "object" && "error" in uploadResponse) { + throw new Error(uploadResponse.error) + } + + const uploadTarget = + typeof uploadResponse === "string" + ? { signedUrl: uploadResponse, publicUrl: undefined } + : uploadResponse - if (typeof signedUrl === "object" && "error" in signedUrl) { - throw new Error(signedUrl.error) + if (uploadTarget.publicUrl) { + uppy.setFileMeta(file.id, { + publicUploadUrl: uploadTarget.publicUrl, + }) } return { method: "PUT", - url: signedUrl, + url: uploadTarget.signedUrl, headers: { "content-type": file.type, }, } }, }) - }, [props.upload, uppy.getPlugin]) + }, [props.upload, uppy.getPlugin, uppy.setFileMeta]) return ( { ariaLabelledBy={props["aria-labelledby"]} className={cn( "editor", - "prose prose-sm", + "prose prose-sm dark:prose-invert", props.singleLine ? "min-h-5" : "min-h-[200px]", // Copied from ui/src/input.tsx "w-full min-w-0 rounded-md border border-input bg-background px-3 py-1 text-base shadow-xs outline-none transition-[color,box-shadow] selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", diff --git a/packages/ui/src/monaco/MonacoEditor.tsx b/packages/ui/src/monaco/MonacoEditor.tsx index 0b50ee0b8f..d7b12b8c0b 100644 --- a/packages/ui/src/monaco/MonacoEditor.tsx +++ b/packages/ui/src/monaco/MonacoEditor.tsx @@ -111,7 +111,7 @@ export const MonacoEditor = React.forwardRef( React.useEffect(() => { if (!monaco || !containerRef.current || editorRef.current) return - const monacoTheme = themeRef.current === "light" ? "pubpub-light" : "pubpub-dark" + const monacoTheme = themeRef.current === "light" ? "pubstar-light" : "pubstar-dark" const newEditor = monaco.editor.create(containerRef.current, { value: valueRef.current, @@ -172,7 +172,7 @@ export const MonacoEditor = React.forwardRef( // update theme when it changes React.useEffect(() => { if (!monaco || !editorRef.current) return - const monacoTheme = currentTheme === "light" ? "pubpub-light" : "pubpub-dark" + const monacoTheme = currentTheme === "light" ? "pubstar-light" : "pubstar-dark" monaco.editor.setTheme(monacoTheme) }, [monaco, currentTheme]) diff --git a/packages/ui/src/monaco/languages/jsonata.ts b/packages/ui/src/monaco/languages/jsonata.ts index 5c89999508..360280885b 100644 --- a/packages/ui/src/monaco/languages/jsonata.ts +++ b/packages/ui/src/monaco/languages/jsonata.ts @@ -196,7 +196,7 @@ export const createJsonataLanguageDefinition = (): languages.IMonarchLanguage => }) export const defineJsonataThemes = (monaco: Monaco) => { - monaco.editor.defineTheme("pubpub-light", { + monaco.editor.defineTheme("pubstar-light", { base: "vs", inherit: true, rules: [ @@ -213,7 +213,7 @@ export const defineJsonataThemes = (monaco: Monaco) => { colors: {}, }) - monaco.editor.defineTheme("pubpub-dark", { + monaco.editor.defineTheme("pubstar-dark", { base: "vs-dark", inherit: true, rules: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dbac2a503..e8a431cbad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ catalogs: version: 20.19.11 '@typescript/native-preview': specifier: latest - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 '@vitejs/plugin-react': specifier: ^4.5.0 version: 4.7.0 @@ -280,7 +280,7 @@ importers: '@prisma/client': specifier: 5.19.1 version: 5.19.1(prisma@5.22.0) - '@pubpub/json-interpolate': + '@pubstar/json-interpolate': specifier: workspace:* version: link:../packages/json-interpolate '@react-email/render': @@ -288,7 +288,7 @@ importers: version: 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@sentry/nextjs': specifier: 'catalog:' - version: 10.5.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)) + version: 10.5.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))) '@sinclair/typebox': specifier: 'catalog:' version: 0.34.30 @@ -560,7 +560,7 @@ importers: '@prisma/internals': specifier: ^5.22.0 version: 5.22.0 - '@pubpub/tailwind': + '@pubstar/tailwind': specifier: workspace:* version: link:../config/tailwind '@storybook/addon-docs': @@ -646,7 +646,7 @@ importers: version: 9.0.8 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 '@vitejs/plugin-react': specifier: 'catalog:' version: 4.7.0(vite@6.3.5(@types/node@20.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) @@ -730,13 +730,13 @@ importers: dependencies: next: specifier: 'catalog:' - version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nextra: specifier: ^4.3.0 - version: 4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2) + version: 4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2) nextra-theme-docs: specifier: ^4.3.0 - version: 4.3.0(@types/react@19.1.10)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)) + version: 4.3.0(@types/react@19.1.10)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)) pagefind: specifier: ^1.3.0 version: 1.3.0 @@ -761,7 +761,7 @@ importers: version: 19.1.7(@types/react@19.1.10) '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 autoprefixer: specifier: 'catalog:' version: 10.4.21(postcss@8.5.6) @@ -786,12 +786,21 @@ importers: jobs: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.1038.0 + version: 3.1038.0 + '@aws-sdk/lib-storage': + specifier: ^3.1038.0 + version: 3.1038.0(@aws-sdk/client-s3@3.1038.0) '@honeycombio/opentelemetry-node': specifier: 'catalog:' version: 0.6.1 '@opentelemetry/auto-instrumentations-node': specifier: 'catalog:' - version: 0.53.0(@opentelemetry/api@1.9.0) + version: 0.53.0(@opentelemetry/api@1.9.1) + '@sentry/node': + specifier: ^10.50.0 + version: 10.50.0 '@ts-rest/core': specifier: 'catalog:' version: 3.51.0(@types/node@20.19.11)(zod@3.25.76) @@ -804,9 +813,18 @@ importers: graphile-worker: specifier: ^0.16.6 version: 0.16.6(typescript@5.9.2) + kysely: + specifier: ^0.27.6 + version: 0.27.6 logger: specifier: workspace:* version: link:../packages/logger + nodemailer: + specifier: ^6.10.1 + version: 6.10.1 + pg: + specifier: ^8.16.3 + version: 8.16.3 react: specifier: catalog:react19 version: 19.2.3 @@ -820,9 +838,15 @@ importers: '@types/node': specifier: 'catalog:' version: 20.19.11 + '@types/nodemailer': + specifier: ^6.4.18 + version: 6.4.18 + '@types/pg': + specifier: ^8.15.5 + version: 8.15.5 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -843,7 +867,7 @@ importers: version: 2.1.1 next: specifier: 'catalog:' - version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: catalog:react19 version: 19.2.3 @@ -865,7 +889,7 @@ importers: version: 19.1.7(@types/react@19.1.10) '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260209.1 + version: 7.0.0-dev.20260430.1 tsconfig: specifier: workspace:* version: link:../config/tsconfig @@ -1121,7 +1145,7 @@ importers: version: 9.0.8 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 '@uiw/react-json-view': specifier: 2.0.0-alpha.27 version: 2.0.0-alpha.27(@babel/runtime@7.28.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -1170,7 +1194,7 @@ importers: version: 3.51.0(@types/node@20.19.11)(zod@3.25.76) '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1204,7 +1228,7 @@ importers: version: 8.15.5 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -1240,7 +1264,7 @@ importers: version: 0.0.31(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 browserslist: specifier: ^4.25.3 version: 4.25.3 @@ -1252,7 +1276,7 @@ importers: version: 19.2.3 react-email: specifier: 3.0.4 - version: 3.0.4(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 3.0.4(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1284,7 +1308,7 @@ importers: version: 2.2.0 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1306,7 +1330,7 @@ importers: version: 20.19.11 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1331,7 +1355,7 @@ importers: devDependencies: '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 react: specifier: catalog:react19 version: 19.2.3 @@ -1514,13 +1538,13 @@ importers: version: 0.55.1 next: specifier: 'catalog:' - version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nuqs: specifier: 'catalog:' - version: 2.4.3(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 2.4.3(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) react-colorful: specifier: ^5.6.1 version: 5.6.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -1557,7 +1581,7 @@ importers: version: 19.1.10 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 react: specifier: catalog:react19 version: 19.2.3 @@ -1582,7 +1606,7 @@ importers: devDependencies: '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260119.1 + version: 7.0.0-dev.20260430.1 tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1590,112 +1614,6 @@ importers: specifier: 'catalog:' version: 5.9.2 - site-builder: - dependencies: - '@astrojs/react': - specifier: ^4.2.4 - version: 4.3.0(@types/node@22.17.2)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - '@aws-sdk/client-s3': - specifier: ^3.525.0 - version: 3.864.0 - '@aws-sdk/lib-storage': - specifier: ^3.787.0 - version: 3.864.0(@aws-sdk/client-s3@3.864.0) - '@hono/node-server': - specifier: ^1.14.1 - version: 1.19.1(hono@4.9.7) - '@hono/zod-validator': - specifier: ^0.4.3 - version: 0.4.3(hono@4.9.7)(zod@3.25.76) - '@pubpub/tailwind': - specifier: workspace:* - version: link:../config/tailwind - '@t3-oss/env-core': - specifier: ^0.12.0 - version: 0.12.0(typescript@5.9.2)(zod@3.25.76) - '@tailwindcss/typography': - specifier: ^0.5.16 - version: 0.5.16(tailwindcss@4.1.17) - '@tailwindcss/vite': - specifier: ^4.1.17 - version: 4.1.17(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) - '@ts-rest/core': - specifier: 'catalog:' - version: 3.51.0(@types/node@22.17.2)(zod@3.25.76) - '@ts-rest/serverless': - specifier: ^3.52.1 - version: 3.52.1(@ts-rest/core@3.51.0(@types/node@22.17.2)(zod@3.25.76))(@types/aws-lambda@8.10.145)(zod@3.25.76) - archiver: - specifier: ^6.0.2 - version: 6.0.2 - astro: - specifier: ^5.7.3 - version: 5.13.5(@types/node@22.17.2)(ioredis@5.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.46.4)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1) - astro-pdf: - specifier: ^1.6.0 - version: 1.7.2(astro@5.13.5(@types/node@22.17.2)(ioredis@5.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.46.4)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1))(typescript@5.9.2) - contracts: - specifier: 'workspace:' - version: link:../packages/contracts - dotenv: - specifier: ^16.4.5 - version: 16.6.1 - hono: - specifier: ^4.9.7 - version: 4.9.7 - mime-types: - specifier: ^2.1.35 - version: 2.1.35 - react: - specifier: catalog:react19 - version: 19.2.3 - react-dom: - specifier: catalog:react19 - version: 19.2.3(react@19.2.3) - tailwindcss: - specifier: 'catalog:' - version: 4.1.17 - tailwindcss-animate: - specifier: ^1.0.6 - version: 1.0.7(tailwindcss@4.1.17) - tsconfig: - specifier: workspace:* - version: link:../config/tsconfig - tsx: - specifier: ^4.19.3 - version: 4.20.5 - ui: - specifier: 'workspace:' - version: link:../packages/ui - utils: - specifier: 'workspace:' - version: link:../packages/utils - zod: - specifier: 'catalog:' - version: 3.25.76 - devDependencies: - '@astrojs/check': - specifier: ^0.9.4 - version: 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.6.2)(typescript@5.9.2) - '@types/archiver': - specifier: ^6.0.2 - version: 6.0.3 - '@types/mime-types': - specifier: ^2.1.4 - version: 2.1.4 - '@types/node': - specifier: '22' - version: 22.17.2 - '@types/react': - specifier: catalog:react19 - version: 19.1.10 - '@types/react-dom': - specifier: catalog:react19 - version: 19.1.7(@types/react@19.1.10) - dotenv-cli: - specifier: ^5.0.1 - version: 5.1.0 - site-builder-2: dependencies: '@aws-sdk/client-s3': @@ -1710,7 +1628,7 @@ importers: '@hono/zod-validator': specifier: ^0.4.3 version: 0.4.3(hono@4.9.7)(zod@3.25.76) - '@pubpub/json-interpolate': + '@pubstar/json-interpolate': specifier: 'workspace:' version: link:../packages/json-interpolate '@t3-oss/env-core': @@ -1801,53 +1719,6 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@astrojs/check@0.9.4': - resolution: {integrity: sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - - '@astrojs/compiler@2.12.2': - resolution: {integrity: sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==} - - '@astrojs/internal-helpers@0.7.2': - resolution: {integrity: sha512-KCkCqR3Goym79soqEtbtLzJfqhTWMyVaizUi35FLzgGSzBotSw8DB1qwsu7U96ihOJgYhDk2nVPz+3LnXPeX6g==} - - '@astrojs/language-server@2.15.4': - resolution: {integrity: sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==} - hasBin: true - peerDependencies: - prettier: ^3.0.0 - prettier-plugin-astro: '>=0.11.0' - peerDependenciesMeta: - prettier: - optional: true - prettier-plugin-astro: - optional: true - - '@astrojs/markdown-remark@6.3.6': - resolution: {integrity: sha512-bwylYktCTsLMVoCOEHbn2GSUA3c5KT/qilekBKA3CBng0bo1TYjNZPr761vxumRk9kJGqTOtU+fgCAp5Vwokug==} - - '@astrojs/prism@3.3.0': - resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - - '@astrojs/react@4.3.0': - resolution: {integrity: sha512-N02aj52Iezn69qHyx5+XvPqgsPMEnel9mI5JMbGiRMTzzLMuNaxRVoQTaq2024Dpr7BLsxCjqMkNvelqMDhaHA==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - peerDependencies: - '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 - '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 - react: ^17.0.2 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 - - '@astrojs/telemetry@3.3.0': - resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - - '@astrojs/yaml2ts@0.2.2': - resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} - '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1871,6 +1742,10 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + '@aws-sdk/client-s3@3.1038.0': + resolution: {integrity: sha512-k60qm50bWkaqNfCJe1z28WaqgpztE0wbWVMZw6ZJcTOGfrWFhsJeLCEqtkH8w00iEozKx9GQwdQXz4G0sMGdKA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-s3@3.864.0': resolution: {integrity: sha512-QGYi9bWliewxumsvbJLLyx9WC0a4DP4F+utygBcq0zwPxaM0xDfBspQvP1dsepi7mW5aAjZmJ2+Xb7X0EhzJ/g==} engines: {node: '>=18.0.0'} @@ -1887,34 +1762,80 @@ packages: resolution: {integrity: sha512-LFUREbobleHEln+Zf7IG83lAZwvHZG0stI7UU0CtwyuhQy5Yx0rKksHNOCmlM7MpTEbSCfntEhYi3jUaY5e5lg==} engines: {node: '>=18.0.0'} + '@aws-sdk/core@3.974.6': + resolution: {integrity: sha512-8Vu7zGxu+39ChR/s5J7nXBw3a2kMHAi0OfKT8ohgTVjX0qYed/8mIfdBb638oBmKrWCwwKjYAM5J/4gMJ8nAJA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.7': + resolution: {integrity: sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.864.0': resolution: {integrity: sha512-StJPOI2Rt8UE6lYjXUpg6tqSZaM72xg46ljPg8kIevtBAAfdtq9K20qT/kSliWGIBocMFAv0g2mC0hAa+ECyvg==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-env@3.972.32': + resolution: {integrity: sha512-7vA4GHg8NSmQxquJHSBcSM3RgB4ZaaRi6u4+zGFKOmOH6aqlgr2Sda46clkZDYzlirgfY96w15Zj0jh6PT48ng==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.864.0': resolution: {integrity: sha512-E/RFVxGTuGnuD+9pFPH2j4l6HvrXzPhmpL8H8nOoJUosjx7d4v93GJMbbl1v/fkDLqW9qN4Jx2cI6PAjohA6OA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-http@3.972.34': + resolution: {integrity: sha512-vBrhWujFCLp1u8ptJRWYlipMutzPptb8pDQ00rKVH9q67T7rGd3VTWIj63aKrlLuY6qSsw1Rt5F/D/7wnNgryA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.864.0': resolution: {integrity: sha512-PlxrijguR1gxyPd5EYam6OfWLarj2MJGf07DvCx9MAuQkw77HBnsu6+XbV8fQriFuoJVTBLn9ROhMr/ROAYfUg==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-ini@3.972.36': + resolution: {integrity: sha512-FBHyCmV8EB0gUvh1d+CZm87zt2PrdC7OyWexLRoH3I5zWSOUGa+9t58Y5jbxRfwUp3AWpHAFvKY6YzgR845sVA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.36': + resolution: {integrity: sha512-IFap01lJKxQc0C/OHmZwZQr/cKq0DhrcmKedRrdnnl42D+P0SImnnnWQjv07uIPqpEdtqmkPXb9TiPYTU+prxQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.864.0': resolution: {integrity: sha512-2BEymFeXURS+4jE9tP3vahPwbYRl0/1MVaFZcijj6pq+nf5EPGvkFillbdBRdc98ZI2NedZgSKu3gfZXgYdUhQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-node@3.972.37': + resolution: {integrity: sha512-/WFixFAAiw8WpmjZcI0l4t3DerXLmVinOIfuotmRZnu2qmsFPoqqmstASz0z8bi1pGdFXzeLzf6bwucM3mZcUQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.864.0': resolution: {integrity: sha512-Zxnn1hxhq7EOqXhVYgkF4rI9MnaO3+6bSg/tErnBQ3F8kDpA7CFU24G1YxwaJXp2X4aX3LwthefmSJHwcVP/2g==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-process@3.972.32': + resolution: {integrity: sha512-uZp4tlGbpczV8QxmtIwOpSkcyGtBRR8/T4BAumRKfAt1nwCig3FSCZvrKl6ARDIDVRYn5p2oRcAsfFR01EgMGA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.864.0': resolution: {integrity: sha512-UPyPNQbxDwHVGmgWdGg9/9yvzuedRQVF5jtMkmP565YX9pKZ8wYAcXhcYdNPWFvH0GYdB0crKOmvib+bmCuwkw==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-sso@3.972.36': + resolution: {integrity: sha512-DsLr0UHMyKzRJKe2bjlwU8q1cfoXg8TIJKV/xwvnalAemiZLOZunFzj/whGnFDZIBVLdnbLiwv5SvRf1+CSwkg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.864.0': resolution: {integrity: sha512-nNcjPN4SYg8drLwqK0vgVeSvxeGQiD0FxOaT38mV2H8cu0C5NzpvA+14Xy+W6vT84dxgmJYKk71Cr5QL2Oz+rA==} engines: {node: '>=18.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.36': + resolution: {integrity: sha512-uzrURO7frJhHQVVNR5zBJcCYeMYflmXcWBK1+MiBym2Dfjh6nXATrMixrmGZi+97Q7ETZ+y/4lUwAy0Nfnznjw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-storage@3.1038.0': + resolution: {integrity: sha512-FEGuFSUL9gNfyWf4KcOgzhLiqQgSSvpML3YPnJbj8k2nSKdgyRznXxg8zd4W+NKoVehtNqXwFBvMXeHyOYlOrg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.1038.0 + '@aws-sdk/lib-storage@3.864.0': resolution: {integrity: sha512-Me/HlMXXPv3tStPQufdwnYGholY14JmmzCdOjhnG7gnaClBEnroZKcHuQhrgMm+KyfbzCQ2+9YHsULOfFrg7Mw==} engines: {node: '>=18.0.0'} @@ -1925,50 +1846,98 @@ packages: resolution: {integrity: sha512-Wcsc7VPLjImQw+CP1/YkwyofMs9Ab6dVq96iS8p0zv0C6YTaMjvillkau4zFfrrrTshdzFWKptIFhKK8Zsei1g==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-bucket-endpoint@3.972.10': + resolution: {integrity: sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-expect-continue@3.862.0': resolution: {integrity: sha512-oG3AaVUJ+26p0ESU4INFn6MmqqiBFZGrebST66Or+YBhteed2rbbFl7mCfjtPWUFgquQlvT1UP19P3LjQKeKpw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-expect-continue@3.972.10': + resolution: {integrity: sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.864.0': resolution: {integrity: sha512-MvakvzPZi9uyP3YADuIqtk/FAcPFkyYFWVVMf5iFs/rCdk0CUzn02Qf4CSuyhbkS6Y0KrAsMgKR4MgklPU79Wg==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.974.14': + resolution: {integrity: sha512-mhTO3amGzYv/DQNbbqZo6UkHquBHlEEVRZwXmjeRqLmy1l9z3xCiFzglPL7n9JpVc2DZc9kjaraAn3JQrueZbw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-host-header@3.862.0': resolution: {integrity: sha512-jDje8dCFeFHfuCAxMDXBs8hy8q9NCTlyK4ThyyfAj3U4Pixly2mmzY2u7b7AyGhWsjJNx8uhTjlYq5zkQPQCYw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-host-header@3.972.10': + resolution: {integrity: sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-location-constraint@3.862.0': resolution: {integrity: sha512-MnwLxCw7Cc9OngEH3SHFhrLlDI9WVxaBkp3oTsdY9JE7v8OE38wQ9vtjaRsynjwu0WRtrctSHbpd7h/QVvtjyA==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-location-constraint@3.972.10': + resolution: {integrity: sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-logger@3.862.0': resolution: {integrity: sha512-N/bXSJznNBR/i7Ofmf9+gM6dx/SPBK09ZWLKsW5iQjqKxAKn/2DozlnE54uiEs1saHZWoNDRg69Ww4XYYSlG1Q==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-logger@3.972.10': + resolution: {integrity: sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-recursion-detection@3.862.0': resolution: {integrity: sha512-KVoo3IOzEkTq97YKM4uxZcYFSNnMkhW/qj22csofLegZi5fk90ztUnnaeKfaEJHfHp/tm1Y3uSoOXH45s++kKQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-recursion-detection@3.972.11': + resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-sdk-s3@3.864.0': resolution: {integrity: sha512-GjYPZ6Xnqo17NnC8NIQyvvdzzO7dm+Ks7gpxD/HsbXPmV2aEfuFveJXneGW9e1BheSKFff6FPDWu8Gaj2Iu1yg==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-sdk-s3@3.972.35': + resolution: {integrity: sha512-lLppaNTAz+wNgLdi4FtHzrlwrGF0ODTnBWHBaFg85SKs0eJ+M+tP5ifrA8f/0lNd+Ak3MC1NGC6RavV3ny4HTg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-ssec@3.862.0': resolution: {integrity: sha512-72VtP7DZC8lYTE2L3Efx2BrD98oe9WTK8X6hmd3WTLkbIjvgWQWIdjgaFXBs8WevsXkewIctfyA3KEezvL5ggw==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-ssec@3.972.10': + resolution: {integrity: sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-user-agent@3.864.0': resolution: {integrity: sha512-wrddonw4EyLNSNBrApzEhpSrDwJiNfjxDm5E+bn8n32BbAojXASH8W8jNpxz/jMgNkkJNxCfyqybGKzBX0OhbQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/middleware-user-agent@3.972.36': + resolution: {integrity: sha512-O2beToxguBvrZFFZ+fFgPbbae8MvyIBjQ6lImee4APHEXXNAD5ZJ2ayLF1mb7rsKw86TM81y5czg82bZncjSjg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/nested-clients@3.864.0': resolution: {integrity: sha512-H1C+NjSmz2y8Tbgh7Yy89J20yD/hVyk15hNoZDbCYkXg0M358KS7KVIEYs8E2aPOCr1sK3HBE819D/yvdMgokA==} engines: {node: '>=18.0.0'} + '@aws-sdk/nested-clients@3.997.4': + resolution: {integrity: sha512-4Sf+WY1lMJzXlw5MiyCMe/UzdILCwvuaHThbqMXS6dfh9gZy3No360I42RXquOI/ULUOhWy2HCyU0Fp20fQGPQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/region-config-resolver@3.862.0': resolution: {integrity: sha512-VisR+/HuVFICrBPY+q9novEiE4b3mvDofWqyvmxHcWM7HumTz9ZQSuEtnlB/92GVM3KDUrR9EmBHNRrfXYZkcQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/region-config-resolver@3.972.13': + resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} + engines: {node: '>=20.0.0'} + '@aws-sdk/s3-request-presigner@3.864.0': resolution: {integrity: sha512-IiVFDxabrqTB1A9qZI6IEa3cOgF2eciUG4UX27HzkMY6UXG0EZhnGkgkgHYMt6j2hGAFOvAh0ogv/XxZLg6Zaw==} engines: {node: '>=18.0.0'} @@ -1977,6 +1946,14 @@ packages: resolution: {integrity: sha512-w2HIn/WIcUyv1bmyCpRUKHXB5KdFGzyxPkp/YK5g+/FuGdnFFYWGfcO8O+How4jwrZTarBYsAHW9ggoKvwr37w==} engines: {node: '>=18.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.23': + resolution: {integrity: sha512-wBbys3Y53Ikly556vyADurKpYQHXS7Jjaskbz+Ga9PZCz7PB/9f3VdKbDlz7dqIzn+xwz7L/a6TR4iXcOi8IRw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1038.0': + resolution: {integrity: sha512-Qniru+9oGGb/HNK/gGZWbV3jsD0k71ngE7qMQ/x6gYNYLd2EOwHCS6E2E6jfkaqO4i0d+nNKmfRy8bNcshKdGQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.864.0': resolution: {integrity: sha512-gTc2QHOBo05SCwVA65dUtnJC6QERvFaPiuppGDSxoF7O5AQNK0UR/kMSenwLqN8b5E1oLYvQTv3C1idJLRX0cg==} engines: {node: '>=18.0.0'} @@ -1985,14 +1962,26 @@ packages: resolution: {integrity: sha512-Bei+RL0cDxxV+lW2UezLbCYYNeJm6Nzee0TpW0FfyTRBhH9C1XQh4+x+IClriXvgBnRquTMMYsmJfvx8iyLKrg==} engines: {node: '>=18.0.0'} + '@aws-sdk/types@3.973.8': + resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-arn-parser@3.804.0': resolution: {integrity: sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-endpoints@3.862.0': resolution: {integrity: sha512-eCZuScdE9MWWkHGM2BJxm726MCmWk/dlHjOKvkM0sN1zxBellBMw5JohNss1Z8/TUmnW2gb9XHTOiHuGjOdksA==} engines: {node: '>=18.0.0'} + '@aws-sdk/util-endpoints@3.996.8': + resolution: {integrity: sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-format-url@3.862.0': resolution: {integrity: sha512-4kd2PYUMA/fAnIcVVwBIDCa2KCuUPrS3ELgScLjBaESP0NN+K163m40U5RbzNec/elOcJHR8lEThzzSb7vXH6w==} engines: {node: '>=18.0.0'} @@ -2004,6 +1993,9 @@ packages: '@aws-sdk/util-user-agent-browser@3.862.0': resolution: {integrity: sha512-BmPTlm0r9/10MMr5ND9E92r8KMZbq5ltYXYpVcUbAsnB1RJ8ASJuRoLne5F7mB3YMx0FJoOTuSq7LdQM3LgW3Q==} + '@aws-sdk/util-user-agent-browser@3.972.10': + resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} + '@aws-sdk/util-user-agent-node@3.864.0': resolution: {integrity: sha512-d+FjUm2eJEpP+FRpVR3z6KzMdx1qwxEYDz8jzNKwxYLBBquaBaP/wfoMtMQKAcbrR7aT9FZVZF7zDgzNxUvQlQ==} engines: {node: '>=18.0.0'} @@ -2013,10 +2005,27 @@ packages: aws-crt: optional: true + '@aws-sdk/util-user-agent-node@3.973.22': + resolution: {integrity: sha512-YTYqTmOUrwbm1h99Ee4y/mVYpFRl0oSO/amtP5cc1BZZWdaAVWs9zj3TkyRHWvR9aI/ZS8m3mS6awXtYUlWyaw==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + '@aws-sdk/xml-builder@3.862.0': resolution: {integrity: sha512-6Ed0kmC1NMbuFTEgNmamAUU1h5gShgxL1hBVLbEzUa3trX5aJBz1vU4bXaBTvOYUAnOHtiy1Ml4AMStd6hJnFA==} engines: {node: '>=18.0.0'} + '@aws-sdk/xml-builder@3.972.21': + resolution: {integrity: sha512-qxNiHUtlrsjTeSlrPWiFkWps7uD6YB4eKzg7eLAFH8jbiHTlt0ePNlo2Xu+WlftP38JIcMaIX4jTUjOlE2ySWw==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -2739,9 +2748,6 @@ packages: '@bundled-es-modules/tough-cookie@0.1.6': resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - '@capsizecss/unpack@2.4.0': - resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==} - '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -2910,27 +2916,6 @@ packages: '@electric-sql/pglite@0.3.7': resolution: {integrity: sha512-5c3mybVrhxu5s47zFZtIGdG8YHkKCBENOmqxnNBjY53ZoDhADY/c5UqBDl159b7qtkzNPtbbb893wL9zi1kAuw==} - '@emmetio/abbreviation@2.3.3': - resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} - - '@emmetio/css-abbreviation@2.1.8': - resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} - - '@emmetio/css-parser@0.4.0': - resolution: {integrity: sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==} - - '@emmetio/html-matcher@1.3.0': - resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} - - '@emmetio/scanner@1.0.4': - resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} - - '@emmetio/stream-reader-utils@0.1.0': - resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} - - '@emmetio/stream-reader@2.2.0': - resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} - '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} @@ -3298,6 +3283,11 @@ packages: resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fastify/otel@0.18.0': + resolution: {integrity: sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} @@ -3399,65 +3389,33 @@ packages: peerDependencies: react: ^16.13 || ^17 || ^18 || ^19 - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - '@img/sharp-darwin-arm64@0.34.3': resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - '@img/sharp-darwin-x64@0.34.3': resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.0': resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.0': resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linux-arm64@1.2.0': resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - '@img/sharp-libvips-linux-arm@1.2.0': resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} cpu: [arm] @@ -3468,64 +3426,32 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linux-x64@1.2.0': resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.0': resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linux-arm64@0.34.3': resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - '@img/sharp-linux-arm@0.34.3': resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3538,59 +3464,30 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.34.3': - resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linux-x64@0.34.3': resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.3': resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linuxmusl-x64@0.34.3': resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - '@img/sharp-wasm32@0.34.3': resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3602,24 +3499,12 @@ packages: cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - '@img/sharp-win32-ia32@0.34.3': resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@img/sharp-win32-x64@0.34.3': resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -4050,6 +3935,9 @@ packages: react: '>= 18.2.0' react-dom: '>= 18.2.0' + '@nodable/entities@2.1.0': + resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} + '@node-rs/argon2-android-arm-eabi@1.7.0': resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} engines: {node: '>= 10'} @@ -4336,6 +4224,18 @@ packages: resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.207.0': + resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.212.0': + resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api-logs@0.214.0': + resolution: {integrity: sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.46.0': resolution: {integrity: sha512-+9BcqfiEDGPXEIo+o3tso/aqGM5dGbGwAkGVp3FPpZ8GlkK1YlaKRd9gMVyPaeRATwvO5wYGGnCsAc/sMMM9Qw==} engines: {node: '>=14'} @@ -4360,6 +4260,10 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + '@opentelemetry/auto-instrumentations-node@0.53.0': resolution: {integrity: sha512-AI3VQX1L2g4Xya8fPE1aahVhvya8/ikU7o2kMbry122Gd4kDVph41pejdOhWa/oNUgPRC6FLJmx7SZZ6/ShVjQ==} engines: {node: '>=14'} @@ -4408,6 +4312,18 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.6.1': + resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.7.0': + resolution: {integrity: sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/exporter-logs-otlp-grpc@0.55.0': resolution: {integrity: sha512-ykqawCL0ILJWyCJlxCPSAlqQXZ6x2bQsxAVUu8S3z22XNqY5SMx0rl2d93XnvnrOwtcfm+sM9ZhbGh/i5AZ9xw==} engines: {node: '>=14'} @@ -4504,6 +4420,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-amqplib@0.61.0': + resolution: {integrity: sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-aws-lambda@0.48.0': resolution: {integrity: sha512-0BJHjCUQwDO5uMCAE1C06LoXcLPK3lWlnT40AORFU9DvT/tFFCjs+KlN3vE39FSlWL7vVzyMVOejdcbDv+xMlw==} engines: {node: '>=14'} @@ -4540,6 +4462,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-connect@0.57.0': + resolution: {integrity: sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-cucumber@0.11.0': resolution: {integrity: sha512-6CyeH678mw5AYbXIY1wtuNL7OsE57+XXk5t5pBeiXsAg0Kh0084/MmBzzCNVOCxn+IN5sjXKtjgVIDHrE/iILA==} engines: {node: '>=14'} @@ -4558,6 +4486,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-dataloader@0.31.0': + resolution: {integrity: sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-dns@0.41.0': resolution: {integrity: sha512-4SovC9rlhBcRzlAmw8PZD3tcP8CfIZ8GJIKJlB5Lca7IDh2A92JpOqzrWFCOJVGFYt7E6YeZJ09b+yb/4Ypa5Q==} engines: {node: '>=14'} @@ -4594,6 +4528,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-fs@0.33.0': + resolution: {integrity: sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-generic-pool@0.41.0': resolution: {integrity: sha512-V0OcN7VH37laZU1pxLixFROBkXrT55E5/MpacShsziAhGqiPZyU1XlCAHBseZ0T7cPfQ8Ux3cp0BAv59hRPt1Q==} engines: {node: '>=14'} @@ -4606,6 +4546,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-generic-pool@0.57.0': + resolution: {integrity: sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-graphql@0.45.0': resolution: {integrity: sha512-NCmL89XZcu9NQAskrYsUHT0PygUiLX90GwjS7kUn72nRAuk/myGg8Zj9YUPwe/OKVJcSLA5Fq755jUHlBQ1odA==} engines: {node: '>=14'} @@ -4618,6 +4564,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-graphql@0.62.0': + resolution: {integrity: sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-grpc@0.55.0': resolution: {integrity: sha512-n2ZH4pRwOy0Vhag/3eKqiyDBwcpUnGgJI9iiIRX7vivE0FMncaLazWphNFezRRaM/LuKwq1TD8pVUvieP68mow==} engines: {node: '>=14'} @@ -4636,12 +4588,24 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-hapi@0.60.0': + resolution: {integrity: sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-http@0.203.0': resolution: {integrity: sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-http@0.214.0': + resolution: {integrity: sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-http@0.55.0': resolution: {integrity: sha512-AO27XSjkgNicfy/YBthskFAwx9VfaO7tChrLaTONTfOWv14GlB3Rs2eTYpywZIHWsW2cR5hvVkcDte4GV0stoA==} engines: {node: '>=14'} @@ -4660,12 +4624,24 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-ioredis@0.62.0': + resolution: {integrity: sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-kafkajs@0.12.0': resolution: {integrity: sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-kafkajs@0.23.0': + resolution: {integrity: sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-kafkajs@0.5.0': resolution: {integrity: sha512-34Jv473IVv5uKFPz9m1ONX4DAnIxPXB5xKW46imq/6Cre7fZf23P2Aa/NQyFhCNymwbcJDMv6+6uU3THGn73lQ==} engines: {node: '>=14'} @@ -4684,6 +4660,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-knex@0.58.0': + resolution: {integrity: sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-koa@0.45.0': resolution: {integrity: sha512-nNdgmOZUkP+yR/yF0RsXapJNioORgnrA2Jl58ExlxyGUbHvHjcSAlNY7dsBljQFHhFYzBOh4NPs3TBbF681+qw==} engines: {node: '>=14'} @@ -4696,6 +4678,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-koa@0.62.0': + resolution: {integrity: sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/instrumentation-lru-memoizer@0.42.0': resolution: {integrity: sha512-536coihEiLB8E9wuSGG4j+f/9QhGQhvbb9WWF3Y+Ogn4Zz89Vm7vIQbre/M5coLLFIzVhLDoBD77QjtE+eXn0g==} engines: {node: '>=14'} @@ -4708,6 +4696,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-lru-memoizer@0.58.0': + resolution: {integrity: sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-memcached@0.41.0': resolution: {integrity: sha512-Qrp+yl6pobVAm2F5AJizopDFtKkxwIzJ8iSnV1TDhbB8O7ct4N9p8rz3WvA3XAikS0bVw9rh/cRgYvb7g6AQcQ==} engines: {node: '>=14'} @@ -4726,6 +4720,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongodb@0.67.0': + resolution: {integrity: sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongoose@0.44.0': resolution: {integrity: sha512-gBwxWvUFxTcXDXiLTqpiM7jyOS27X5x8saQesG8RsL128yxAoN3oiy3Hn3hIw13nkh+AHTXBTiADVD/lkazuiA==} engines: {node: '>=14'} @@ -4738,6 +4738,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mongoose@0.60.0': + resolution: {integrity: sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql2@0.43.0': resolution: {integrity: sha512-9W1AxMfrZV3ZeYBPjz8bkMRIRf1od4h+QZLw+m575lu41DMQIprcHXRZbyZRXZG+tgqM3YNBiNZCI2bDV3x46Q==} engines: {node: '>=14'} @@ -4750,6 +4756,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql2@0.60.0': + resolution: {integrity: sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql@0.43.0': resolution: {integrity: sha512-Yd4QLENitUAovh5JKbDIvzLVkt+3InnQYiWqcD4X7VjUGdVlZuCgMNkyUl6ML3WonH60jDy7S2rmLZAlWm7qTg==} engines: {node: '>=14'} @@ -4762,6 +4774,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-mysql@0.60.0': + resolution: {integrity: sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-nestjs-core@0.42.0': resolution: {integrity: sha512-+JRi91A2Ue8JOY7WJ3oSq4HFB6+qIQQ62uu77fKLqV0xn0ft8YX/hDJceUJEKgqPlJMbHH5ppZlCrSPc/d3t0w==} engines: {node: '>=14'} @@ -4786,6 +4804,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-pg@0.66.0': + resolution: {integrity: sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-pino@0.44.0': resolution: {integrity: sha512-nyu6A1Zq3z/GUsfIJLsEMmUZrdqdVeQSESx8i7PzvUiVYyEdvf8w1sg4oPCBrSwl0PFU7FR4uYR4d04/QxFCoA==} engines: {node: '>=14'} @@ -4810,6 +4834,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-redis@0.62.0': + resolution: {integrity: sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-restify@0.43.0': resolution: {integrity: sha512-gNO8cAF7lPCCcWOPlx17LLTKKz2+jKkHI4OGhNoM+yUCG2KXBD5cZ8+XzL/EVLRL0GXHgV4Un4eeBnCUjXYTOw==} engines: {node: '>=14'} @@ -4840,6 +4870,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-tedious@0.33.0': + resolution: {integrity: sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-undici@0.14.0': resolution: {integrity: sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==} engines: {node: ^18.19.0 || >=20.6.0} @@ -4864,6 +4900,24 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.207.0': + resolution: {integrity: sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.212.0': + resolution: {integrity: sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.214.0': + resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.46.0': resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==} engines: {node: '>=14'} @@ -4963,6 +5017,10 @@ packages: resolution: {integrity: sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==} engines: {node: ^18.19.0 || >=20.6.0} + '@opentelemetry/redis-common@0.38.3': + resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} + engines: {node: ^18.19.0 || >=20.6.0} + '@opentelemetry/resource-detector-alibaba-cloud@0.29.7': resolution: {integrity: sha512-PExUl/R+reSQI6Y/eNtgAsk6RHk1ElYSzOa8/FHfdc/nLmx9sqMasBEpLMkETkzDP7t27ORuXe4F9vwkV2uwwg==} engines: {node: '>=14'} @@ -5017,6 +5075,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.7.0': + resolution: {integrity: sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-logs@0.46.0': resolution: {integrity: sha512-Knlyk4+G72uEzNh6GRN1Fhmrj+/rkATI5/lOrevN7zRDLgp4kfyZBGGoWk7w+qQjlYvwhIIdPVxlIcipivdZIg==} engines: {node: '>=14'} @@ -5072,6 +5136,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.7.0': + resolution: {integrity: sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-node@1.19.0': resolution: {integrity: sha512-TCiEq/cUjM15RFqBRwWomTVbOqzndWL4ILa7ZCu0zbjU1/XY6AgHkgrgAc7vGP6TjRqH4Xryuglol8tcIfbBUQ==} engines: {node: '>=14'} @@ -5100,6 +5170,10 @@ packages: resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.40.1': resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} engines: {node: '>=14'} @@ -5112,6 +5186,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@oslojs/asn1@1.0.0': resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==} @@ -5218,6 +5298,11 @@ packages: peerDependencies: '@opentelemetry/api': ^1.8 + '@prisma/instrumentation@7.6.0': + resolution: {integrity: sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==} + peerDependencies: + '@opentelemetry/api': ^1.8 + '@prisma/internals@5.0.0': resolution: {integrity: sha512-VGWyFk6QlSBXT8z65Alq5F3o9E8IiTtaBoa3rmKkGpZjUk85kJy3jZz4xkRv53TaeghGE5rWfwkfak26KtY5yQ==} @@ -5263,16 +5348,6 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@puppeteer/browsers@2.10.8': - resolution: {integrity: sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==} - engines: {node: '>=18'} - hasBin: true - - '@puppeteer/browsers@2.6.1': - resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==} - engines: {node: '>=18'} - hasBin: true - '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -5877,95 +5952,111 @@ packages: '@react-email/body@0.0.11': resolution: {integrity: sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/button@0.0.19': resolution: {integrity: sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-block@0.0.11': resolution: {integrity: sha512-4D43p+LIMjDzm66gTDrZch0Flkip5je91mAT7iGs6+SbPyalHgIA+lFQoQwhz/VzHHLxuD0LV6gwmU/WUQ2WEg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-inline@0.0.5': resolution: {integrity: sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/column@0.0.13': resolution: {integrity: sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/components@0.0.31': resolution: {integrity: sha512-rQsTY9ajobncix9raexhBjC7O6cXUMc87eNez2gnB1FwtkUO8DqWZcktbtwOJi7GKmuAPTx0o/IOFtiBNXziKA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/container@0.0.15': resolution: {integrity: sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/font@0.0.9': resolution: {integrity: sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/head@0.0.12': resolution: {integrity: sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/heading@0.0.15': resolution: {integrity: sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/hr@0.0.11': resolution: {integrity: sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/html@0.0.11': resolution: {integrity: sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/img@0.0.11': resolution: {integrity: sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/link@0.0.12': resolution: {integrity: sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/markdown@0.0.14': resolution: {integrity: sha512-5IsobCyPkb4XwnQO8uFfGcNOxnsg3311GRXhJ3uKv51P7Jxme4ycC/MITnwIZ10w2zx7HIyTiqVzTj4XbuIHbg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/preview@0.0.12': resolution: {integrity: sha512-g/H5fa9PQPDK6WUEG7iTlC19sAktI23qyoiJtMLqQiXFCfWeQMhqjLGKeLSKkfzszqmfJCjZtpSiKtBoOdxp3Q==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -5986,24 +6077,28 @@ packages: '@react-email/row@0.0.12': resolution: {integrity: sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/section@0.0.16': resolution: {integrity: sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/tailwind@1.0.4': resolution: {integrity: sha512-tJdcusncdqgvTUYZIuhNC6LYTfL9vNTSQpwWdTCQhQ1lsrNCEE4OKCSdzSV3S9F32pi0i0xQ+YPJHKIzGjdTSA==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/text@0.0.11': resolution: {integrity: sha512-a7nl/2KLpRHOYx75YbYZpWspUbX1DFY7JIZbOv5x0QU8SvwDbJt+Hm01vG34PffFyYvHEXrc6Qnip2RTjljNjg==} engines: {node: '>=18.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -6336,6 +6431,10 @@ packages: resolution: {integrity: sha512-jTJ8NhZSKB2yj3QTVRXfCCngQzAOLThQUxCl9A7Mv+XF10tP7xbH/88MVQ5WiOr2IzcmrB9r2nmUe36BnMlLjA==} engines: {node: '>=18'} + '@sentry/core@10.50.0': + resolution: {integrity: sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==} + engines: {node: '>=18'} + '@sentry/nextjs@10.5.0': resolution: {integrity: sha512-CWozbPqbAX8qUx4DdVLgjEkjcG+JJ5vHyGczo8yiWVQQZAv/Ivd+TVxqAVMJiL68y+C4VQYfejGp64zsIYS3yw==} engines: {node: '>=18'} @@ -6354,10 +6453,38 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 '@opentelemetry/semantic-conventions': ^1.34.0 + '@sentry/node-core@10.50.0': + resolution: {integrity: sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/exporter-trace-otlp-http': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + '@opentelemetry/semantic-conventions': + optional: true + '@sentry/node@10.5.0': resolution: {integrity: sha512-GqTkOc7tkWqRTKNjipysElh/bzIkhfLsvNGwH6+zel5kU15IdOCFtAqIri85ZLo9vbaIVtjQELXOzfo/5MMAFQ==} engines: {node: '>=18'} + '@sentry/node@10.50.0': + resolution: {integrity: sha512-TvwzFQu8MGKzMQ2/tqxcNzFA8UG2kKTB+GDmA4uOzx3+GT849YZRRSJzEXCmYhk1teVd2fbmgqyYY2nyLF5a+Q==} + engines: {node: '>=18'} + '@sentry/opentelemetry@10.5.0': resolution: {integrity: sha512-/Qva5vngtuh79YUUBA8kbbrD6w/A+u1vy1jnLoPMKDxWTfNPqT4tCiOOmWYotnITaE3QO0UtXK/j7LMX8FhtUA==} engines: {node: '>=18'} @@ -6368,6 +6495,15 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 '@opentelemetry/semantic-conventions': ^1.34.0 + '@sentry/opentelemetry@10.50.0': + resolution: {integrity: sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + '@sentry/react@10.5.0': resolution: {integrity: sha512-UHanvg+oIAvE/Hm76QCCdxYgb+tIuF0JszQoROApl5C5RxRfJJcU643pASQs6BDvrtxbuMQ/AHTacLTYpsn0cg==} engines: {node: '>=18'} @@ -6421,14 +6557,30 @@ packages: resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} engines: {node: '>=18.0.0'} + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} + '@smithy/chunked-blob-reader@5.0.0': resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} engines: {node: '>=18.0.0'} + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} + '@smithy/config-resolver@4.1.5': resolution: {integrity: sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==} engines: {node: '>=18.0.0'} + '@smithy/config-resolver@4.4.17': + resolution: {integrity: sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.17': + resolution: {integrity: sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==} + engines: {node: '>=18.0.0'} + '@smithy/core@3.8.0': resolution: {integrity: sha512-EYqsIYJmkR1VhVE9pccnk353xhs+lB6btdutJEtsp7R055haMJp2yE16eSxw8fv+G0WUY6vqxyYOP8kOqawxYQ==} engines: {node: '>=18.0.0'} @@ -6437,46 +6589,90 @@ packages: resolution: {integrity: sha512-dDzrMXA8d8riFNiPvytxn0mNwR4B3h8lgrQ5UjAGu6T9z/kRg/Xncf4tEQHE/+t25sY8IH3CowcmWi+1U5B1Gw==} engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.2.14': + resolution: {integrity: sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.0.5': resolution: {integrity: sha512-miEUN+nz2UTNoRYRhRqVTJCx7jMeILdAurStT2XoS+mhokkmz1xAPp95DFW9Gxt4iF2VBqpeF9HbTQ3kY1viOA==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.2.14': + resolution: {integrity: sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.0.5': resolution: {integrity: sha512-LCUQUVTbM6HFKzImYlSB9w4xafZmpdmZsOh9rIl7riPC3osCgGFVP+wwvYVw6pXda9PPT9TcEZxaq3XE81EdJQ==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.2.14': + resolution: {integrity: sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-config-resolver@4.1.3': resolution: {integrity: sha512-yTTzw2jZjn/MbHu1pURbHdpjGbCuMHWncNBpJnQAPxOVnFUAbSIUSwafiphVDjNV93TdBJWmeVAds7yl5QCkcA==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-config-resolver@4.3.14': + resolution: {integrity: sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-node@4.0.5': resolution: {integrity: sha512-lGS10urI4CNzz6YlTe5EYG0YOpsSp3ra8MXyco4aqSkQDuyZPIw2hcaxDU82OUVtK7UY9hrSvgWtpsW5D4rb4g==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-node@4.2.14': + resolution: {integrity: sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-universal@4.0.5': resolution: {integrity: sha512-JFnmu4SU36YYw3DIBVao3FsJh4Uw65vVDIqlWT4LzR6gXA0F3KP0IXFKKJrhaVzCBhAuMsrUUaT5I+/4ZhF7aw==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-universal@4.2.14': + resolution: {integrity: sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==} + engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.1.1': resolution: {integrity: sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==} engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.3.17': + resolution: {integrity: sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==} + engines: {node: '>=18.0.0'} + '@smithy/hash-blob-browser@4.0.5': resolution: {integrity: sha512-F7MmCd3FH/Q2edhcKd+qulWkwfChHbc9nhguBlVjSUE6hVHhec3q6uPQ+0u69S6ppvLtR3eStfCuEKMXBXhvvA==} engines: {node: '>=18.0.0'} + '@smithy/hash-blob-browser@4.2.15': + resolution: {integrity: sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==} + engines: {node: '>=18.0.0'} + '@smithy/hash-node@4.0.5': resolution: {integrity: sha512-cv1HHkKhpyRb6ahD8Vcfb2Hgz67vNIXEp2vnhzfxLFGRukLCNEA5QdsorbUEzXma1Rco0u3rx5VTqbM06GcZqQ==} engines: {node: '>=18.0.0'} + '@smithy/hash-node@4.2.14': + resolution: {integrity: sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==} + engines: {node: '>=18.0.0'} + '@smithy/hash-stream-node@4.0.5': resolution: {integrity: sha512-IJuDS3+VfWB67UC0GU0uYBG/TA30w+PlOaSo0GPm9UHS88A6rCP6uZxNjNYiyRtOcjv7TXn/60cW8ox1yuZsLg==} engines: {node: '>=18.0.0'} + '@smithy/hash-stream-node@4.2.14': + resolution: {integrity: sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==} + engines: {node: '>=18.0.0'} + '@smithy/invalid-dependency@4.0.5': resolution: {integrity: sha512-IVnb78Qtf7EJpoEVo7qJ8BEXQwgC4n3igeJNNKEj/MLYtapnx8A67Zt/J3RXAj2xSO1910zk0LdFiygSemuLow==} engines: {node: '>=18.0.0'} + '@smithy/invalid-dependency@4.2.14': + resolution: {integrity: sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==} + engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} @@ -6485,70 +6681,142 @@ packages: resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==} engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + '@smithy/md5-js@4.0.5': resolution: {integrity: sha512-8n2XCwdUbGr8W/XhMTaxILkVlw2QebkVTn5tm3HOcbPbOpWg89zr6dPXsH8xbeTsbTXlJvlJNTQsKAIoqQGbdA==} engines: {node: '>=18.0.0'} + '@smithy/md5-js@4.2.14': + resolution: {integrity: sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-content-length@4.0.5': resolution: {integrity: sha512-l1jlNZoYzoCC7p0zCtBDE5OBXZ95yMKlRlftooE5jPWQn4YBPLgsp+oeHp7iMHaTGoUdFqmHOPa8c9G3gBsRpQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-content-length@4.2.14': + resolution: {integrity: sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.1.18': resolution: {integrity: sha512-ZhvqcVRPZxnZlokcPaTwb+r+h4yOIOCJmx0v2d1bpVlmP465g3qpVSf7wxcq5zZdu4jb0H4yIMxuPwDJSQc3MQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-endpoint@4.4.32': + resolution: {integrity: sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.1.19': resolution: {integrity: sha512-X58zx/NVECjeuUB6A8HBu4bhx72EoUz+T5jTMIyeNKx2lf+Gs9TmWPNNkH+5QF0COjpInP/xSpJGJ7xEnAklQQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-retry@4.5.7': + resolution: {integrity: sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.0.9': resolution: {integrity: sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==} engines: {node: '>=18.0.0'} + '@smithy/middleware-serde@4.2.20': + resolution: {integrity: sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==} + engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.0.5': resolution: {integrity: sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==} engines: {node: '>=18.0.0'} + '@smithy/middleware-stack@4.2.14': + resolution: {integrity: sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==} + engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.1.4': resolution: {integrity: sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==} engines: {node: '>=18.0.0'} + '@smithy/node-config-provider@4.3.14': + resolution: {integrity: sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==} + engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.1.1': resolution: {integrity: sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.6.1': + resolution: {integrity: sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.0.5': resolution: {integrity: sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==} engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.14': + resolution: {integrity: sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==} + engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.1.3': resolution: {integrity: sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==} engines: {node: '>=18.0.0'} + '@smithy/protocol-http@5.3.14': + resolution: {integrity: sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==} + engines: {node: '>=18.0.0'} + '@smithy/querystring-builder@4.0.5': resolution: {integrity: sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==} engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@4.0.5': + '@smithy/querystring-builder@4.2.14': + resolution: {integrity: sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.0.5': resolution: {integrity: sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==} engines: {node: '>=18.0.0'} + '@smithy/querystring-parser@4.2.14': + resolution: {integrity: sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==} + engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.0.7': resolution: {integrity: sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==} engines: {node: '>=18.0.0'} + '@smithy/service-error-classification@4.3.1': + resolution: {integrity: sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==} + engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.0.5': resolution: {integrity: sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==} engines: {node: '>=18.0.0'} + '@smithy/shared-ini-file-loader@4.4.9': + resolution: {integrity: sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.1.3': resolution: {integrity: sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==} engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.3.14': + resolution: {integrity: sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.13': + resolution: {integrity: sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==} + engines: {node: '>=18.0.0'} + '@smithy/smithy-client@4.4.10': resolution: {integrity: sha512-iW6HjXqN0oPtRS0NK/zzZ4zZeGESIFcxj2FkWed3mcK8jdSdHzvnCKXSjvewESKAgGKAbJRA+OsaqKhkdYRbQQ==} engines: {node: '>=18.0.0'} + '@smithy/types@4.14.1': + resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.3.2': resolution: {integrity: sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==} engines: {node: '>=18.0.0'} @@ -6557,18 +6825,34 @@ packages: resolution: {integrity: sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==} engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.14': + resolution: {integrity: sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-base64@4.0.0': resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} engines: {node: '>=18.0.0'} + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@4.0.0': resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@4.0.0': resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} engines: {node: '>=18.0.0'} + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -6577,42 +6861,82 @@ packages: resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==} engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@4.0.0': resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} engines: {node: '>=18.0.0'} + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@4.0.26': resolution: {integrity: sha512-xgl75aHIS/3rrGp7iTxQAOELYeyiwBu+eEgAk4xfKwJJ0L8VUjhO2shsDpeil54BOFsqmk5xfdesiewbUY5tKQ==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-browser@4.3.49': + resolution: {integrity: sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==} + engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@4.0.26': resolution: {integrity: sha512-z81yyIkGiLLYVDetKTUeCZQ8x20EEzvQjrqJtb/mXnevLq2+w3XCEWTJ2pMp401b6BkEkHVfXb/cROBpVauLMQ==} engines: {node: '>=18.0.0'} + '@smithy/util-defaults-mode-node@4.2.54': + resolution: {integrity: sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==} + engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@3.0.7': resolution: {integrity: sha512-klGBP+RpBp6V5JbrY2C/VKnHXn3d5V2YrifZbmMY8os7M6m8wdYFoO6w/fe5VkP+YVwrEktW3IWYaSQVNZJ8oQ==} engines: {node: '>=18.0.0'} + '@smithy/util-endpoints@3.4.2': + resolution: {integrity: sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==} + engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@4.0.0': resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==} engines: {node: '>=18.0.0'} + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.0.5': resolution: {integrity: sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==} engines: {node: '>=18.0.0'} + '@smithy/util-middleware@4.2.14': + resolution: {integrity: sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==} + engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.0.7': resolution: {integrity: sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==} engines: {node: '>=18.0.0'} + '@smithy/util-retry@4.3.6': + resolution: {integrity: sha512-p6/FO1n2KxMeQyna067i0uJ6TSbb165ZhnRtCpWh4Foxqbfc6oW+XITaL8QkFJj3KFnDe2URt4gOhgU06EP9ew==} + engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.2.4': resolution: {integrity: sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==} engines: {node: '>=18.0.0'} + '@smithy/util-stream@4.5.25': + resolution: {integrity: sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==} + engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.0.0': resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} engines: {node: '>=18.0.0'} + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + '@smithy/util-utf8@2.3.0': resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} @@ -6621,10 +6945,22 @@ packages: resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==} engines: {node: '>=18.0.0'} + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + '@smithy/util-waiter@4.0.7': resolution: {integrity: sha512-mYqtQXPmrwvUljaHyGxYUIIRI3qjBTEb/f5QFi3A6VlxhpmZd5mWXn9W+qUkf2pVE1Hv3SqxefiZOPGdxmO64A==} engines: {node: '>=18.0.0'} + '@smithy/util-waiter@4.3.0': + resolution: {integrity: sha512-JyjYmLAfS+pdxF92o4yLgEoy0zhayKTw73FU1aofLWwLcJw7iSqIY2exGmMTrl/lmZugP5p/zxdFSippJDfKWA==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -7024,11 +7360,6 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - '@tailwindcss/vite@4.1.17': - resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - '@tanstack/query-core@5.85.5': resolution: {integrity: sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==} @@ -7377,9 +7708,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/fontkit@2.0.8': - resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==} - '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -7490,12 +7818,18 @@ packages: '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + '@types/pg@8.15.4': resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} '@types/pg@8.15.5': resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/pg@8.6.1': resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} @@ -7570,85 +7904,51 @@ packages: '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-siuRD9Shh5gVrgYG5HEWxFxG/dkZa4ndupGWKMfM4DwMG7zLeFayi6sB9yiwpD0d203ts01D7uTnTCALdiWXmQ==} - cpu: [arm64] - os: [darwin] - - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-TyFP7dGMo/Xz37MI3QNfGl3J2i8AKurYwLLD+bG0EDLWnz213wwBwN6U9vMcyatBzfdxKEHHPgdNP0UYCVx3kQ==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-1Rk5JoMlmlF4GzeNxQmpaZSSPFa056DCrGLjhXwVIqRf8+pGNKKxyD2ugGSyOeLShqYb1XUoE9LhsAhdh1xgSA==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-ivqxCrbLXUqZU1OMojVRCnVx5gC/twgi7gKzBXMBLGOgfTkhajbHk/71J3OQhJwzR3T2ISG6FTfXKHhQMtgkkg==} - cpu: [x64] - os: [darwin] - - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-1Dr8toDQcmqKjXd5cQoTAjzMR46cscaojQiazbAPJsU/1PQFgBT36/Mb/epLpzN+ZKKgf7Xd6u2eqH2ze0kF6Q==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-wjXJtELfI0QIYxGCqqKMR3DonPUlMP4aWOYRPiN5ylDtdV+OqCC16zvH7C+No7xvlf8dDxlV1ZTyuRCWL6CQmw==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-ttNri2Ui1CzlLnPJN0sQ4XBgrCMq4jjtxouitRGh7+YlToG561diLERjOwIhNfTzPDKRMS7XO090WoepbvzFpA==} - cpu: [arm64] - os: [linux] - - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-xmGrxP0ERLeczerjJtask6gOln/QhAeELqTmaNoATvU7hZfEzDDxJOgSXZnX6bCIQHdN/Xn49gsyPjzTaK4rAg==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-PeCFDB1glivpkqqKsQJ1RrM4f5B1yXzXFF+eKwgEZ+evcQ3N+BgeR7BpuRhOpHU9ixJchKjU1bXgcHOAaM+Rkw==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-Bev1d6NCgCmcGOgmdFG514tWRt2lNUSFjQ9RVnN86tSm+bl5p9Lv6TQjc38Ow9vY11J71IZs9HNN1AKWfBCj2Q==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-3eqYkqy1XpbIJC1XkGbkwAvTtSCw6dSjYzJaw9bvow4fS1totTFZP/2K9ecXQ3gIZaPS4Ome/SpkZHl1cy9eZA==} + engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-svmoHHjs5gDekSDW6yLzk9iyDxhMnLKJZ9Xk6b1bSz0swrQNPPTJdR7mbhVMrv4HtXei0LHPlXdTr85AqI5qOQ==} - cpu: [arm] - os: [linux] - - '@typescript/native-preview-linux-x64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-mwsjGZqUKju3SKPzlDuKhKgt9Ht8seA5OBhorvRZk2B5lwlH0gDsApGK4t50TcnzjpbWI85FVxI6wTq1T36dMg==} - cpu: [x64] - os: [linux] - - '@typescript/native-preview-linux-x64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-cK4XK3L7TXPj9fIalQcXRqSErdM+pZSqiNgp6QtNsNCyoH2W6J281hnjUA4TmD4TRMSn8CRn7Exy3CGNC3gZkA==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-Eg7nbRV57ayq0Pjuott/36UbQlVlpz2YRVLM5h8RyXx6SwvgrdYxNv/1zULLB0UlWdyKkK3bXILmR8YmNvbl+g==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-463QnUaRCUhY/Flj/XinORTbBYuxoMthgJiBU1vu7mipLo2Yaipkkgn1ArGHkV9mjWBa7QIPCWg/V2KIEoVdcA==} - cpu: [arm64] - os: [win32] - - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-U919FWN5FZG/1i75+Cv9mnd80Mw2rdFE/to/wJ6DX9m0dUL8IfZARQYPGDXDO1LEC6sV3CyCpCJ/HqsSkqgaAg==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-0LAjufJKUZnHmp0bxlndjphDQlIeUXA0czZCrKENtKySeGMXrM9PAFtSx94ldWVXDTZ9ZP1r+FxbIvP9pRORzA==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-039WAg5xJjqrRYVHMR9Y2y83dYSLofbyx/22Gc6ur3b/nR8u1wdErK9uwrguL3lxpKDo6qdhnkGlbX8FP0Bz+g==} - cpu: [x64] - os: [win32] - - '@typescript/native-preview-win32-x64@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-1U/2fG/A1yZtkP59IkDlOVLw2cPtP6NbLROtTytNN0CLSqme+0OXoh+l7wlN2iSmGY5zIeaVcqs4UIL0SiQInQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-FvLWX7d3b/IhL0656tnjep7dOMnI7CPrg+5oI1MKIY74qgvR+8VRo6aGj0IRCwtAJeklpxBpljEcIGuS5Yc/pQ==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260119.1': - resolution: {integrity: sha512-Tf74TdJVJlLRMN0W9VXK8jc0Gor9+wFRm40qTLt2JeHiPpSF5TEN/pHPjlf4Id1wDSJXH9p5/U1wFS3s5TS2PQ==} - hasBin: true - - '@typescript/native-preview@7.0.0-dev.20260209.1': - resolution: {integrity: sha512-UdA8RC9ic/qi9ajolQQP7ZG8YwtUbxtTMu6FxKBn4pYWicuXqMjzXqH/Ng+VlqqeYrl088P4Ou0erGPuLu4ajw==} + '@typescript/native-preview@7.0.0-dev.20260430.1': + resolution: {integrity: sha512-HHk3tPpzPKqHY4AHMxGK6i960Dd1kIvnSnT3mzD1hNO/sUVG2YdWvWBIActGkRnKS94AZBpekOr1bQP7O2OwIg==} + engines: {node: '>=16.20.0'} hasBin: true '@typescript/vfs@1.6.1': @@ -7906,32 +8206,6 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@volar/kit@2.4.23': - resolution: {integrity: sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ==} - peerDependencies: - typescript: '*' - - '@volar/language-core@2.4.23': - resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} - - '@volar/language-server@2.4.23': - resolution: {integrity: sha512-k0iO+tybMGMMyrNdWOxgFkP0XJTdbH0w+WZlM54RzJU3WZSjHEupwL30klpM7ep4FO6qyQa03h+VcGHD4Q8gEg==} - - '@volar/language-service@2.4.23': - resolution: {integrity: sha512-h5mU9DZ/6u3LCB9xomJtorNG6awBNnk9VuCioGsp6UtFiM8amvS5FcsaC3dabdL9zO0z+Gq9vIEMb/5u9K6jGQ==} - - '@volar/source-map@2.4.23': - resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} - - '@volar/typescript@2.4.23': - resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - - '@vscode/emmet-helper@2.11.0': - resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} - - '@vscode/l10n@0.0.18': - resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} - '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -7980,6 +8254,7 @@ packages: '@xmldom/xmldom@0.9.8': resolution: {integrity: sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -8180,17 +8455,6 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - astro-pdf@1.7.2: - resolution: {integrity: sha512-uhoqXm58jcHwCRbmkEgtXnISygEjJHOrp77lFFkgW68R9RPt0SlxFZxB9mA2gpJ3RsitrEL3/iinJuB7ZJ9m1A==} - engines: {node: '>=18.0.0'} - peerDependencies: - astro: ^4.4.4 || ^5.0.0 - - astro@5.13.5: - resolution: {integrity: sha512-XmBzkl13XU97+n/QiOM5uXQdAVe0yKt5gO+Wlgc8dHRwHR499qhMQ5sMFckLJweUINLzcNGjP3F5nG4wV8a2XA==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} - hasBin: true - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -8223,10 +8487,6 @@ packages: axios@1.11.0: resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -8255,39 +8515,13 @@ 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} + bare-events@2.6.1: resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} - bare-fs@4.2.2: - resolution: {integrity: sha512-5vn+bdnlCYMwETIm1FqQXDP6TYPbxr2uJd88ve40kr4oPbiTZJVrTNzqA3/4sfWZeWKuQR/RkboBt7qEEDtfMA==} - engines: {bare: '>=1.16.0'} - peerDependencies: - bare-buffer: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - - bare-os@3.6.2: - resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} - engines: {bare: '>=1.14.0'} - - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - - bare-stream@2.7.0: - resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} - peerDependencies: - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - bare-events: - optional: true - - base-64@1.0.0: - resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} - base16@1.0.0: resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==} @@ -8301,6 +8535,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} @@ -8321,9 +8556,6 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - blob-to-buffer@1.2.9: - resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==} - bowser@2.12.0: resolution: {integrity: sha512-HcOcTudTeEWgbHh0Y1Tyb6fdeR71m4b/QACf0D4KswGTsNeIJQmg38mRENZPAYPZvGFN3fk3604XbQEPdxXdKg==} @@ -8331,23 +8563,20 @@ packages: resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} engines: {node: '>=14.16'} - boxen@8.0.1: - resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} - engines: {node: '>=18'} - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - brotli@1.3.3: - resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} - browserslist@4.25.3: resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -8406,10 +8635,6 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - camelcase@8.0.0: - resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} - engines: {node: '>=16'} - caniuse-lite@1.0.30001735: resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} @@ -8519,11 +8744,6 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - chromium-bidi@0.11.0: - resolution: {integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==} - peerDependencies: - devtools-protocol: '*' - ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -8532,13 +8752,12 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.3.0: - resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} - engines: {node: '>=8'} - cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -8607,10 +8826,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -8694,9 +8909,6 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - common-ancestor-path@1.0.1: - resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -8750,17 +8962,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.2: - resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} - engines: {node: '>=18'} - core-js-compat@3.45.0: resolution: {integrity: sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==} @@ -8793,15 +8998,6 @@ packages: typescript: optional: true - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -8824,9 +9020,6 @@ packages: cropperjs@1.6.2: resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} - cross-fetch@3.2.0: - resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -8835,17 +9028,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crossws@0.3.5: - resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} - crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -9137,9 +9323,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -9167,9 +9350,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -9181,22 +9361,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - deterministic-object-hash@2.0.2: - resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} - engines: {node: '>=18'} - - devalue@5.3.2: - resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - devtools-protocol@0.0.1367902: - resolution: {integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==} - - dfa@1.2.0: - resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} - diacritics@1.3.0: resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==} @@ -9207,17 +9374,10 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -9271,10 +9431,6 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dset@3.1.4: - resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} - engines: {node: '>=4'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -9288,12 +9444,6 @@ packages: electron-to-chromium@1.5.207: resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} - emmet@2.4.11: - resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} - - emoji-regex@10.5.0: - resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -9514,11 +9664,6 @@ packages: engines: {node: '>=16.0.0'} hasBin: true - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} @@ -9557,19 +9702,23 @@ packages: fast-write-atomic@0.2.1: resolution: {integrity: sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==} + fast-xml-builder@1.1.5: + resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==} + fast-xml-parser@5.2.5: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true + fast-xml-parser@5.7.2: + resolution: {integrity: sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==} + hasBin: true + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -9610,10 +9759,6 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} - flattie@1.1.1: - resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} - engines: {node: '>=8'} - follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -9623,12 +9768,6 @@ packages: debug: optional: true - fontace@0.3.0: - resolution: {integrity: sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg==} - - fontkit@2.0.4: - resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -9722,10 +9861,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.3.1: - resolution: {integrity: sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -9742,10 +9877,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -9787,20 +9918,23 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -9810,6 +9944,7 @@ packages: glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -9874,9 +10009,6 @@ packages: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} - h3@1.15.4: - resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} - hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -10021,9 +10153,6 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-escaper@3.0.3: - resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} @@ -10044,9 +10173,6 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - http-proxy-agent@7.0.0: resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} engines: {node: '>= 14'} @@ -10118,8 +10244,9 @@ packages: import-in-the-middle@1.14.2: resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + import-in-the-middle@3.0.1: + resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} + engines: {node: '>=18'} indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -10181,9 +10308,6 @@ packages: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} - iron-webcrypto@1.2.1: - resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -10582,17 +10706,10 @@ packages: resolution: {integrity: sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w==} engines: {node: '>= 8'} - jsonc-parser@2.3.1: - resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsondiffpatch@0.4.1: resolution: {integrity: sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==} engines: {node: '>=8.17.0'} hasBin: true - bundledDependencies: [] jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -11088,9 +11205,6 @@ packages: peerDependencies: react: ^18.0 || ^19.0 - mdast-util-definitions@6.0.0: - resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} - mdast-util-directive@3.1.0: resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} @@ -11148,9 +11262,6 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -11339,6 +11450,10 @@ packages: resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -11422,9 +11537,6 @@ packages: mudder@2.1.1: resolution: {integrity: sha512-0/F//kjoRlefsazFcGxa7FAuwRNDoX3ALal7W9uOZgE9QKxKatFM1NKu3tkmxMAFvUXoIHN2b/PlIt5B+hJirQ==} - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -11456,10 +11568,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - neotraverse@0.6.18: - resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} - engines: {node: '>= 10'} - netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -11521,9 +11629,6 @@ packages: no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-fetch@2.6.11: resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} @@ -11551,9 +11656,6 @@ packages: encoding: optional: true - node-mock-http@1.0.2: - resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==} - node-plop@0.26.3: resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} engines: {node: '>=8.9.4'} @@ -11657,12 +11759,6 @@ packages: resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} engines: {node: '>=0.10.0'} - ofetch@1.4.1: - resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -11748,10 +11844,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@6.2.0: - resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} - engines: {node: '>=18'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -11818,9 +11910,6 @@ packages: resolution: {integrity: sha512-8KPLGT5g9s+olKMRTU9LFekLizkVIu9tes90O1/aigJ0T5LmyPqTzGJrETnSw3meSYg58YH7JTzhTTW/3z6VAw==} hasBin: true - pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} - param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} @@ -11874,6 +11963,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -11920,9 +12013,6 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - pg-cloudflare@1.2.7: resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} @@ -12089,15 +12179,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-astro@0.14.1: - resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} - engines: {node: ^14.15.0 || >=16.0.0} - - prettier@2.8.7: - resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} - engines: {node: '>=10.13.0'} - hasBin: true - prettier@3.6.2: resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} @@ -12245,16 +12326,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@23.11.1: - resolution: {integrity: sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==} - engines: {node: '>=18'} - - puppeteer@23.11.1: - resolution: {integrity: sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==} - engines: {node: '>=18'} - deprecated: < 24.15.0 is no longer supported - hasBin: true - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -12275,9 +12346,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - radix3@1.1.2: - resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - ramda@0.30.1: resolution: {integrity: sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==} @@ -12637,12 +12705,6 @@ packages: resolution: {integrity: sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q==} engines: {node: '>=8'} - request-light@0.5.8: - resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} - - request-light@0.7.0: - resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -12655,6 +12717,10 @@ packages: resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} engines: {node: '>=8.6.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -12690,9 +12756,6 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - restructure@3.0.2: - resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} - retext-latin@4.0.0: resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} @@ -12768,9 +12831,6 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - s.color@0.0.15: - resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} - safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -12796,9 +12856,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-formatter@0.7.9: - resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} - saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -12882,10 +12939,6 @@ packages: shallow-equal@3.1.0: resolution: {integrity: sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.3: resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -12964,10 +13017,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smol-toml@1.4.2: - resolution: {integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==} - engines: {node: '>= 18'} - snake-case@2.1.0: resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==} @@ -13102,10 +13151,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -13173,6 +13218,9 @@ packages: strnum@2.1.1: resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + strnum@2.2.3: + resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} + style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} @@ -13214,9 +13262,6 @@ packages: stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - suf-log@2.5.3: - resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} - supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -13271,9 +13316,6 @@ packages: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} - tar-fs@3.1.0: - resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} - tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -13284,6 +13326,7 @@ packages: tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tarn@3.0.2: resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} @@ -13351,9 +13394,6 @@ packages: resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} engines: {node: '>=8'} - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -13608,9 +13648,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typed-query-selector@2.12.0: - resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typedarray.prototype.slice@1.0.5: resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} engines: {node: '>= 0.4'} @@ -13618,12 +13655,6 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typesafe-path@0.2.2: - resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - - typescript-auto-import-cache@0.3.6: - resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} - typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -13640,19 +13671,10 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - ultrahtml@1.6.0: - resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - - uncrypto@0.1.3: - resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -13671,16 +13693,10 @@ packages: resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} engines: {node: '>=4'} - unicode-properties@1.4.1: - resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} - unicode-property-aliases-ecmascript@2.1.0: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} - unicode-trie@2.0.0: - resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} - unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -13688,9 +13704,6 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unifont@0.5.2: - resolution: {integrity: sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg==} - unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -13755,68 +13768,6 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} - unstorage@1.17.0: - resolution: {integrity: sha512-l9Z7lBiwtNp8ZmcoZ/dmPkFXFdtEdZtTZafCSnEIj3YvtkXeGAtL2rN8MQFy/0cs4eOLpuRJMp9ivdug7TCvww==} - peerDependencies: - '@azure/app-configuration': ^1.8.0 - '@azure/cosmos': ^4.2.0 - '@azure/data-tables': ^13.3.0 - '@azure/identity': ^4.6.0 - '@azure/keyvault-secrets': ^4.9.0 - '@azure/storage-blob': ^12.26.0 - '@capacitor/preferences': ^6.0.3 || ^7.0.0 - '@deno/kv': '>=0.9.0' - '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 - '@planetscale/database': ^1.19.0 - '@upstash/redis': ^1.34.3 - '@vercel/blob': '>=0.27.1' - '@vercel/functions': ^2.2.12 - '@vercel/kv': ^1.0.1 - aws4fetch: ^1.0.20 - db0: '>=0.2.1' - idb-keyval: ^6.2.1 - ioredis: ^5.4.2 - uploadthing: ^7.4.4 - peerDependenciesMeta: - '@azure/app-configuration': - optional: true - '@azure/cosmos': - optional: true - '@azure/data-tables': - optional: true - '@azure/identity': - optional: true - '@azure/keyvault-secrets': - optional: true - '@azure/storage-blob': - optional: true - '@capacitor/preferences': - optional: true - '@deno/kv': - optional: true - '@netlify/blobs': - optional: true - '@planetscale/database': - optional: true - '@upstash/redis': - optional: true - '@vercel/blob': - optional: true - '@vercel/functions': - optional: true - '@vercel/kv': - optional: true - aws4fetch: - optional: true - db0: - optional: true - idb-keyval: - optional: true - ioredis: - optional: true - uploadthing: - optional: true - update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -13894,19 +13845,22 @@ packages: uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -13995,14 +13949,6 @@ packages: yaml: optional: true - vitefu@1.1.1: - resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 - peerDependenciesMeta: - vite: - optional: true - vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -14031,115 +13977,26 @@ packages: jsdom: optional: true - volar-service-css@0.0.62: - resolution: {integrity: sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-emmet@0.0.62: - resolution: {integrity: sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-html@0.0.62: - resolution: {integrity: sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-prettier@0.0.62: - resolution: {integrity: sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==} - peerDependencies: - '@volar/language-service': ~2.4.0 - prettier: ^2.2 || ^3.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - prettier: - optional: true - - volar-service-typescript-twoslash-queries@0.0.62: - resolution: {integrity: sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-typescript@0.0.62: - resolution: {integrity: sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-yaml@0.0.62: - resolution: {integrity: sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - vscode-css-languageservice@6.3.7: - resolution: {integrity: sha512-5TmXHKllPzfkPhW4UE9sODV3E0bIOJPOk+EERKllf2SmAczjfTmYeq5txco+N3jpF8KIZ6loj/JptpHBQuVQRA==} - - vscode-html-languageservice@5.5.1: - resolution: {integrity: sha512-/ZdEtsZ3OiFSyL00kmmu7crFV9KwWR+MgpzjsxO60DQH7sIfHZM892C/E4iDd11EKocr+NYuvOA4Y7uc3QzLEA==} - - vscode-json-languageservice@4.1.8: - resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} - engines: {npm: '>=7.0.0'} - - vscode-jsonrpc@6.0.0: - resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} - engines: {node: '>=8.0.0 || >=10.0.0'} - vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} - vscode-languageserver-protocol@3.16.0: - resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} - 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.16.0: - resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} - vscode-languageserver-types@3.17.5: resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - vscode-languageserver@7.0.0: - resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} - hasBin: true - vscode-languageserver@9.0.1: resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} hasBin: true - vscode-nls@5.2.0: - resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} - vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -14187,6 +14044,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -14211,10 +14069,6 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-pm-runs@1.1.0: - resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} - engines: {node: '>=4'} - which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} @@ -14236,10 +14090,6 @@ packages: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} - widest-line@5.0.0: - resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} - engines: {node: '>=18'} - wildcard@1.1.2: resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} @@ -14262,10 +14112,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -14304,9 +14150,6 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - xxhash-wasm@1.1.0: - resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -14321,18 +14164,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yaml-language-server@1.15.0: - resolution: {integrity: sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==} - hasBin: true - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.2.2: - resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} - engines: {node: '>= 14'} - yaml@2.3.1: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} @@ -14350,9 +14185,6 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yjs@13.6.19: resolution: {integrity: sha512-GNKw4mEUn5yWU2QPHRx8jppxmCm9KzbBhB4qJLUJFiiYD0g/tDVgXQ7aPkyh01YO28kbs2J/BEbWBagjuWyejw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -14369,18 +14201,10 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} - yocto-spinner@0.2.3: - resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} - engines: {node: '>=18.19'} - yoctocolors-cjs@2.1.2: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - zip-stream@4.1.1: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} @@ -14389,26 +14213,12 @@ packages: resolution: {integrity: sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==} engines: {node: '>= 12.0.0'} - zod-to-json-schema@3.24.6: - resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} - peerDependencies: - zod: ^3.24.1 - - zod-to-ts@1.2.0: - resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} - peerDependencies: - typescript: ^4.9.4 || ^5.0.2 - zod: ^3 - zod-validation-error@5.0.0: resolution: {integrity: sha512-hmk+pkyKq7Q71PiWVSDUc3VfpzpvcRHZ3QPw9yEMVvmtCekaMeOHnbr3WbxfrgEnQTv6haGP4cmv0Ojmihzsxw==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -14485,133 +14295,23 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 - '@astrojs/check@0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.6.2)(typescript@5.9.2)': - dependencies: - '@astrojs/language-server': 2.15.4(prettier-plugin-astro@0.14.1)(prettier@3.6.2)(typescript@5.9.2) - chokidar: 4.0.3 - kleur: 4.1.5 - typescript: 5.9.2 - yargs: 17.7.2 - transitivePeerDependencies: - - prettier - - prettier-plugin-astro - - '@astrojs/compiler@2.12.2': {} - - '@astrojs/internal-helpers@0.7.2': {} - - '@astrojs/language-server@2.15.4(prettier-plugin-astro@0.14.1)(prettier@3.6.2)(typescript@5.9.2)': - dependencies: - '@astrojs/compiler': 2.12.2 - '@astrojs/yaml2ts': 0.2.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@volar/kit': 2.4.23(typescript@5.9.2) - '@volar/language-core': 2.4.23 - '@volar/language-server': 2.4.23 - '@volar/language-service': 2.4.23 - fast-glob: 3.3.3 - muggle-string: 0.4.1 - volar-service-css: 0.0.62(@volar/language-service@2.4.23) - volar-service-emmet: 0.0.62(@volar/language-service@2.4.23) - volar-service-html: 0.0.62(@volar/language-service@2.4.23) - volar-service-prettier: 0.0.62(@volar/language-service@2.4.23)(prettier@3.6.2) - volar-service-typescript: 0.0.62(@volar/language-service@2.4.23) - volar-service-typescript-twoslash-queries: 0.0.62(@volar/language-service@2.4.23) - volar-service-yaml: 0.0.62(@volar/language-service@2.4.23) - vscode-html-languageservice: 5.5.1 - vscode-uri: 3.1.0 - optionalDependencies: - prettier: 3.6.2 - prettier-plugin-astro: 0.14.1 - transitivePeerDependencies: - - typescript - - '@astrojs/markdown-remark@6.3.6': - dependencies: - '@astrojs/internal-helpers': 0.7.2 - '@astrojs/prism': 3.3.0 - github-slugger: 2.0.0 - hast-util-from-html: 2.0.3 - hast-util-to-text: 4.0.2 - import-meta-resolve: 4.2.0 - js-yaml: 4.1.0 - mdast-util-definitions: 6.0.0 - rehype-raw: 7.0.0 - rehype-stringify: 10.0.1 - remark-gfm: 4.0.1 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remark-smartypants: 3.0.2 - shiki: 3.11.0 - smol-toml: 1.4.2 - unified: 11.0.5 - unist-util-remove-position: 5.0.0 - unist-util-visit: 5.0.0 - unist-util-visit-parents: 6.0.1 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@astrojs/prism@3.3.0': - dependencies: - prismjs: 1.30.0 - - '@astrojs/react@4.3.0(@types/node@22.17.2)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)': - dependencies: - '@types/react': 19.1.10 - '@types/react-dom': 19.1.7(@types/react@19.1.10) - '@vitejs/plugin-react': 4.7.0(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - ultrahtml: 1.6.0 - vite: 6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@astrojs/telemetry@3.3.0': - dependencies: - ci-info: 4.3.0 - debug: 4.4.1 - dlv: 1.1.3 - dset: 3.1.4 - is-docker: 3.0.0 - is-wsl: 3.1.0 - which-pm-runs: 1.1.0 - transitivePeerDependencies: - - supports-color - - '@astrojs/yaml2ts@0.2.2': - dependencies: - yaml: 2.8.1 - '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 '@aws-sdk/util-locate-window': 3.804.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -14621,7 +14321,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 '@aws-sdk/util-locate-window': 3.804.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -14629,7 +14329,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -14638,10 +14338,70 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.862.0 + '@aws-sdk/types': 3.973.8 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + '@aws-sdk/client-s3@3.1038.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.6 + '@aws-sdk/credential-provider-node': 3.972.37 + '@aws-sdk/middleware-bucket-endpoint': 3.972.10 + '@aws-sdk/middleware-expect-continue': 3.972.10 + '@aws-sdk/middleware-flexible-checksums': 3.974.14 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-location-constraint': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-sdk-s3': 3.972.35 + '@aws-sdk/middleware-ssec': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.36 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.23 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.22 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/eventstream-serde-browser': 4.2.14 + '@smithy/eventstream-serde-config-resolver': 4.3.14 + '@smithy/eventstream-serde-node': 4.2.14 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-blob-browser': 4.2.15 + '@smithy/hash-node': 4.2.14 + '@smithy/hash-stream-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/md5-js': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-stream': 4.5.25 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-s3@3.864.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 @@ -14720,32 +14480,32 @@ snapshots: '@aws-sdk/util-endpoints': 3.862.0 '@aws-sdk/util-user-agent-browser': 3.862.0 '@aws-sdk/util-user-agent-node': 3.864.0 - '@smithy/config-resolver': 4.1.5 - '@smithy/core': 3.8.0 - '@smithy/fetch-http-handler': 5.1.1 - '@smithy/hash-node': 4.0.5 - '@smithy/invalid-dependency': 4.0.5 - '@smithy/middleware-content-length': 4.0.5 - '@smithy/middleware-endpoint': 4.1.18 - '@smithy/middleware-retry': 4.1.19 - '@smithy/middleware-serde': 4.0.9 - '@smithy/middleware-stack': 4.0.5 - '@smithy/node-config-provider': 4.1.4 - '@smithy/node-http-handler': 4.1.1 - '@smithy/protocol-http': 5.1.3 - '@smithy/smithy-client': 4.4.10 - '@smithy/types': 4.3.2 - '@smithy/url-parser': 4.0.5 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.26 - '@smithy/util-defaults-mode-node': 4.0.26 - '@smithy/util-endpoints': 3.0.7 - '@smithy/util-middleware': 4.0.5 - '@smithy/util-retry': 4.0.7 - '@smithy/util-utf8': 4.0.0 - '@smithy/util-waiter': 4.0.7 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.3.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14764,31 +14524,31 @@ snapshots: '@aws-sdk/util-endpoints': 3.862.0 '@aws-sdk/util-user-agent-browser': 3.862.0 '@aws-sdk/util-user-agent-node': 3.864.0 - '@smithy/config-resolver': 4.1.5 - '@smithy/core': 3.8.0 - '@smithy/fetch-http-handler': 5.1.1 - '@smithy/hash-node': 4.0.5 - '@smithy/invalid-dependency': 4.0.5 - '@smithy/middleware-content-length': 4.0.5 - '@smithy/middleware-endpoint': 4.1.18 - '@smithy/middleware-retry': 4.1.19 - '@smithy/middleware-serde': 4.0.9 - '@smithy/middleware-stack': 4.0.5 - '@smithy/node-config-provider': 4.1.4 - '@smithy/node-http-handler': 4.1.1 - '@smithy/protocol-http': 5.1.3 - '@smithy/smithy-client': 4.4.10 - '@smithy/types': 4.3.2 - '@smithy/url-parser': 4.0.5 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.26 - '@smithy/util-defaults-mode-node': 4.0.26 - '@smithy/util-endpoints': 3.0.7 - '@smithy/util-middleware': 4.0.5 - '@smithy/util-retry': 4.0.7 - '@smithy/util-utf8': 4.0.0 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -14811,6 +14571,28 @@ snapshots: fast-xml-parser: 5.2.5 tslib: 2.8.1 + '@aws-sdk/core@3.974.6': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/xml-builder': 3.972.21 + '@smithy/core': 3.23.17 + '@smithy/node-config-provider': 4.3.14 + '@smithy/property-provider': 4.2.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/signature-v4': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.7': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -14819,6 +14601,14 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.32': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -14832,6 +14622,19 @@ snapshots: '@smithy/util-stream': 4.2.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.34': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/types': 3.973.8 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/node-http-handler': 4.6.1 + '@smithy/property-provider': 4.2.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/util-stream': 4.5.25 + tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -14850,6 +14653,38 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-ini@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/credential-provider-env': 3.972.32 + '@aws-sdk/credential-provider-http': 3.972.34 + '@aws-sdk/credential-provider-login': 3.972.36 + '@aws-sdk/credential-provider-process': 3.972.32 + '@aws-sdk/credential-provider-sso': 3.972.36 + '@aws-sdk/credential-provider-web-identity': 3.972.36 + '@aws-sdk/nested-clients': 3.997.4 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.2.14 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/nested-clients': 3.997.4 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-node@3.864.0': dependencies: '@aws-sdk/credential-provider-env': 3.864.0 @@ -14867,6 +14702,23 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-node@3.972.37': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.32 + '@aws-sdk/credential-provider-http': 3.972.34 + '@aws-sdk/credential-provider-ini': 3.972.36 + '@aws-sdk/credential-provider-process': 3.972.32 + '@aws-sdk/credential-provider-sso': 3.972.36 + '@aws-sdk/credential-provider-web-identity': 3.972.36 + '@aws-sdk/types': 3.973.8 + '@smithy/credential-provider-imds': 4.2.14 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-process@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -14876,6 +14728,15 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.32': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-sso@3.864.0': dependencies: '@aws-sdk/client-sso': 3.864.0 @@ -14889,6 +14750,19 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-sso@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/nested-clients': 3.997.4 + '@aws-sdk/token-providers': 3.1038.0 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/credential-provider-web-identity@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -14900,6 +14774,30 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/credential-provider-web-identity@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/nested-clients': 3.997.4 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/lib-storage@3.1038.0(@aws-sdk/client-s3@3.1038.0)': + dependencies: + '@aws-sdk/client-s3': 3.1038.0 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + '@aws-sdk/lib-storage@3.864.0(@aws-sdk/client-s3@3.864.0)': dependencies: '@aws-sdk/client-s3': 3.864.0 @@ -14921,6 +14819,16 @@ snapshots: '@smithy/util-config-provider': 4.0.0 tslib: 2.8.1 + '@aws-sdk/middleware-bucket-endpoint@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/middleware-expect-continue@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 @@ -14928,6 +14836,13 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-expect-continue@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/middleware-flexible-checksums@3.864.0': dependencies: '@aws-crypto/crc32': 5.2.0 @@ -14944,6 +14859,23 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@aws-sdk/middleware-flexible-checksums@3.974.14': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.974.6 + '@aws-sdk/crc64-nvme': 3.972.7 + '@aws-sdk/types': 3.973.8 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-stream': 4.5.25 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 @@ -14951,18 +14883,37 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-host-header@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/middleware-location-constraint@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-location-constraint@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-logger@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/middleware-recursion-detection@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 @@ -14970,7 +14921,15 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.864.0': + '@aws-sdk/middleware-recursion-detection@3.972.11': + dependencies: + '@aws-sdk/types': 3.973.8 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 '@aws-sdk/types': 3.862.0 @@ -14987,12 +14946,35 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3@3.972.35': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.17 + '@smithy/node-config-provider': 4.3.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/signature-v4': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-stream': 4.5.25 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/middleware-ssec@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-ssec@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -15003,6 +14985,17 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/middleware-user-agent@3.972.36': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@smithy/core': 3.23.17 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-retry': 4.3.6 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.864.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -15017,31 +15010,75 @@ snapshots: '@aws-sdk/util-endpoints': 3.862.0 '@aws-sdk/util-user-agent-browser': 3.862.0 '@aws-sdk/util-user-agent-node': 3.864.0 - '@smithy/config-resolver': 4.1.5 - '@smithy/core': 3.8.0 - '@smithy/fetch-http-handler': 5.1.1 - '@smithy/hash-node': 4.0.5 - '@smithy/invalid-dependency': 4.0.5 - '@smithy/middleware-content-length': 4.0.5 - '@smithy/middleware-endpoint': 4.1.18 - '@smithy/middleware-retry': 4.1.19 - '@smithy/middleware-serde': 4.0.9 - '@smithy/middleware-stack': 4.0.5 - '@smithy/node-config-provider': 4.1.4 - '@smithy/node-http-handler': 4.1.1 - '@smithy/protocol-http': 5.1.3 - '@smithy/smithy-client': 4.4.10 - '@smithy/types': 4.3.2 - '@smithy/url-parser': 4.0.5 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.26 - '@smithy/util-defaults-mode-node': 4.0.26 - '@smithy/util-endpoints': 3.0.7 - '@smithy/util-middleware': 4.0.5 - '@smithy/util-retry': 4.0.7 - '@smithy/util-utf8': 4.0.0 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/nested-clients@3.997.4': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.6 + '@aws-sdk/middleware-host-header': 3.972.10 + '@aws-sdk/middleware-logger': 3.972.10 + '@aws-sdk/middleware-recursion-detection': 3.972.11 + '@aws-sdk/middleware-user-agent': 3.972.36 + '@aws-sdk/region-config-resolver': 3.972.13 + '@aws-sdk/signature-v4-multi-region': 3.996.23 + '@aws-sdk/types': 3.973.8 + '@aws-sdk/util-endpoints': 3.996.8 + '@aws-sdk/util-user-agent-browser': 3.972.10 + '@aws-sdk/util-user-agent-node': 3.973.22 + '@smithy/config-resolver': 4.4.17 + '@smithy/core': 3.23.17 + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/hash-node': 4.2.14 + '@smithy/invalid-dependency': 4.2.14 + '@smithy/middleware-content-length': 4.2.14 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-retry': 4.5.7 + '@smithy/middleware-serde': 4.2.20 + '@smithy/middleware-stack': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/node-http-handler': 4.6.1 + '@smithy/protocol-http': 5.3.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.49 + '@smithy/util-defaults-mode-node': 4.2.54 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/util-utf8': 4.2.2 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -15055,6 +15092,14 @@ snapshots: '@smithy/util-middleware': 4.0.5 tslib: 2.8.1 + '@aws-sdk/region-config-resolver@3.972.13': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/config-resolver': 4.4.17 + '@smithy/node-config-provider': 4.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/s3-request-presigner@3.864.0': dependencies: '@aws-sdk/signature-v4-multi-region': 3.864.0 @@ -15075,6 +15120,27 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.23': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.35 + '@aws-sdk/types': 3.973.8 + '@smithy/protocol-http': 5.3.14 + '@smithy/signature-v4': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1038.0': + dependencies: + '@aws-sdk/core': 3.974.6 + '@aws-sdk/nested-clients': 3.997.4 + '@aws-sdk/types': 3.973.8 + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/token-providers@3.864.0': dependencies: '@aws-sdk/core': 3.864.0 @@ -15082,7 +15148,7 @@ snapshots: '@aws-sdk/types': 3.862.0 '@smithy/property-provider': 4.0.5 '@smithy/shared-ini-file-loader': 4.0.5 - '@smithy/types': 4.3.2 + '@smithy/types': 4.14.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -15092,10 +15158,19 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/types@3.973.8': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.804.0': dependencies: tslib: 2.8.1 + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 @@ -15104,6 +15179,14 @@ snapshots: '@smithy/util-endpoints': 3.0.7 tslib: 2.8.1 + '@aws-sdk/util-endpoints@3.996.8': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-endpoints': 3.4.2 + tslib: 2.8.1 + '@aws-sdk/util-format-url@3.862.0': dependencies: '@aws-sdk/types': 3.862.0 @@ -15122,6 +15205,13 @@ snapshots: bowser: 2.12.0 tslib: 2.8.1 + '@aws-sdk/util-user-agent-browser@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.8 + '@smithy/types': 4.14.1 + bowser: 2.12.0 + tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.864.0': dependencies: '@aws-sdk/middleware-user-agent': 3.864.0 @@ -15130,11 +15220,29 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/util-user-agent-node@3.973.22': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.36 + '@aws-sdk/types': 3.973.8 + '@smithy/node-config-provider': 4.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + '@aws-sdk/xml-builder@3.862.0': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.21': + dependencies: + '@nodable/entities': 2.1.0 + '@smithy/types': 4.14.1 + fast-xml-parser: 5.7.2 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -16047,14 +16155,6 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@capsizecss/unpack@2.4.0': - dependencies: - blob-to-buffer: 1.2.9 - cross-fetch: 3.2.0 - fontkit: 2.0.4 - transitivePeerDependencies: - - encoding - '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 @@ -16316,29 +16416,6 @@ snapshots: '@electric-sql/pglite@0.3.7': {} - '@emmetio/abbreviation@2.3.3': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/css-abbreviation@2.1.8': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/css-parser@0.4.0': - dependencies: - '@emmetio/stream-reader': 2.2.0 - '@emmetio/stream-reader-utils': 0.1.0 - - '@emmetio/html-matcher@1.3.0': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/scanner@1.0.4': {} - - '@emmetio/stream-reader-utils@0.1.0': {} - - '@emmetio/stream-reader@2.2.0': {} - '@emnapi/core@0.45.0': dependencies: tslib: 2.8.1 @@ -16597,6 +16674,16 @@ snapshots: '@faker-js/faker@9.9.0': {} + '@fastify/otel@0.18.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.212.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 @@ -16742,92 +16829,48 @@ snapshots: dependencies: react: 19.2.3 - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true - '@img/sharp-darwin-arm64@0.34.3': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.0 optional: true - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - '@img/sharp-darwin-x64@0.34.3': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.2.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-arm64@1.2.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-x64@1.2.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linux-arm64@1.2.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - '@img/sharp-libvips-linux-arm@1.2.0': optional: true '@img/sharp-libvips-linux-ppc64@1.2.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - '@img/sharp-libvips-linux-s390x@1.2.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - '@img/sharp-libvips-linux-x64@1.2.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.0': optional: true - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - '@img/sharp-linux-arm64@0.34.3': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.2.0 optional: true - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - '@img/sharp-linux-arm@0.34.3': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.2.0 @@ -16838,51 +16881,26 @@ snapshots: '@img/sharp-libvips-linux-ppc64': 1.2.0 optional: true - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - '@img/sharp-linux-s390x@0.34.3': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.2.0 optional: true - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - '@img/sharp-linux-x64@0.34.3': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.2.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-arm64@0.34.3': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-x64@0.34.3': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.2.0 optional: true - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.4.5 - optional: true - '@img/sharp-wasm32@0.34.3': dependencies: '@emnapi/runtime': 1.4.5 @@ -16891,15 +16909,9 @@ snapshots: '@img/sharp-win32-arm64@0.34.3': optional: true - '@img/sharp-win32-ia32@0.33.5': - optional: true - '@img/sharp-win32-ia32@0.34.3': optional: true - '@img/sharp-win32-x64@0.33.5': - optional: true - '@img/sharp-win32-x64@0.34.3': optional: true @@ -17440,6 +17452,8 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + '@nodable/entities@2.1.0': {} + '@node-rs/argon2-android-arm-eabi@1.7.0': optional: true @@ -17654,6 +17668,18 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.207.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api-logs@0.212.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api-logs@0.214.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.46.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -17672,6 +17698,8 @@ snapshots: '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.1': {} + '@opentelemetry/auto-instrumentations-node@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17726,6 +17754,60 @@ snapshots: - encoding - supports-color + '@opentelemetry/auto-instrumentations-node@0.53.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-aws-lambda': 0.48.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-aws-sdk': 0.47.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-bunyan': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-cassandra-driver': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-connect': 0.41.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-cucumber': 0.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dataloader': 0.14.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dns': 0.41.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-express': 0.45.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fastify': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fs': 0.17.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-generic-pool': 0.41.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-graphql': 0.45.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-grpc': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-hapi': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-ioredis': 0.45.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-kafkajs': 0.5.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-knex': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-koa': 0.45.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-lru-memoizer': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-memcached': 0.41.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongodb': 0.49.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongoose': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql2': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-nestjs-core': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-net': 0.41.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pg': 0.48.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pino': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-redis': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-redis-4': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-restify': 0.43.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-router': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-socket.io': 0.44.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-tedious': 0.16.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-undici': 0.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-winston': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-alibaba-cloud': 0.29.7(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-aws': 1.12.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-azure': 0.3.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-container': 0.5.3(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-gcp': 0.30.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - encoding + - supports-color + '@opentelemetry/context-async-hooks@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -17734,6 +17816,10 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17748,16 +17834,36 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/exporter-logs-otlp-grpc@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@grpc/grpc-js': 1.13.4 @@ -17767,6 +17873,15 @@ snapshots: '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17776,6 +17891,15 @@ snapshots: '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17787,6 +17911,17 @@ snapshots: '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@grpc/grpc-js': 1.13.4 @@ -17838,6 +17973,16 @@ snapshots: '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -17856,6 +18001,15 @@ snapshots: '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -17875,6 +18029,15 @@ snapshots: '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -17891,6 +18054,14 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/exporter-zipkin@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/instrumentation-amqplib@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17900,6 +18071,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-amqplib@0.44.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-amqplib@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17909,6 +18089,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-amqplib@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-aws-lambda@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17918,6 +18107,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-aws-lambda@0.48.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/aws-lambda': 8.10.143 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-aws-sdk@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17928,6 +18126,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-aws-sdk@0.47.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagation-utils': 0.30.16(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-bunyan@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17937,6 +18145,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-bunyan@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@types/bunyan': 1.8.9 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-cassandra-driver@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17945,6 +18162,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-cassandra-driver@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-connect@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17955,6 +18180,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-connect@0.41.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/connect': 3.4.36 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-connect@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17965,6 +18200,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-connect@0.57.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-cucumber@0.11.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17973,6 +18218,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-cucumber@0.11.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-dataloader@0.14.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17980,6 +18233,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-dataloader@0.14.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-dataloader@0.21.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17987,6 +18247,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-dataloader@0.31.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-dns@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -17994,6 +18261,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-dns@0.41.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-express@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18003,6 +18277,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-express@0.45.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-express@0.52.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18021,6 +18304,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-fastify@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-fs@0.17.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18029,6 +18321,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-fs@0.17.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-fs@0.23.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18037,6 +18337,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-fs@0.33.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-generic-pool@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18044,6 +18352,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-generic-pool@0.41.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-generic-pool@0.47.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18051,6 +18366,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-generic-pool@0.57.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-graphql@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18058,6 +18380,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-graphql@0.45.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-graphql@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18065,6 +18394,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-graphql@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-grpc@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18073,6 +18409,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-grpc@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-hapi@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18082,6 +18426,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-hapi@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-hapi@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18091,6 +18444,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-hapi@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-http@0.203.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18101,6 +18463,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-http@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18112,6 +18484,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-http@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + forwarded-parse: 2.1.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-ioredis@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18121,6 +18504,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-ioredis@0.45.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-ioredis@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18130,6 +18522,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-ioredis@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-kafkajs@0.12.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18138,6 +18539,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-kafkajs@0.23.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-kafkajs@0.5.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18146,6 +18555,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-kafkajs@0.5.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-knex@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18154,6 +18571,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-knex@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-knex@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18162,6 +18587,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-knex@0.58.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-koa@0.45.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18171,6 +18604,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-koa@0.45.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-koa@0.51.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18180,6 +18622,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-koa@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18187,6 +18638,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18194,6 +18652,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-lru-memoizer@0.58.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-memcached@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18203,6 +18668,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-memcached@0.41.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/memcached': 2.2.10 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongodb@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18211,6 +18685,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mongodb@0.49.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongodb@0.56.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18219,6 +18701,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mongodb@0.67.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongoose@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18228,6 +18718,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mongoose@0.44.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mongoose@0.50.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18237,6 +18736,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mongoose@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mysql2@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18246,6 +18754,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql2@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mysql2@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18255,6 +18772,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql2@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mysql@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18264,6 +18790,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@types/mysql': 2.15.26 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-mysql@0.49.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18273,6 +18808,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-mysql@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-nestjs-core@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18281,6 +18825,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-nestjs-core@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-net@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18289,6 +18841,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-net@0.41.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-pg@0.48.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18301,6 +18861,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-pg@0.48.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.1) + '@types/pg': 8.6.1 + '@types/pg-pool': 2.0.6 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-pg@0.55.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18313,6 +18885,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-pg@0.66.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-pino@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18322,6 +18906,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-pino@0.44.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-redis-4@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18331,53 +18924,114 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis@0.44.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-redis-4@0.44.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.44.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.44.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.36.2 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.51.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.0 + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-restify@0.43.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-restify@0.43.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-router@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.36.2 '@opentelemetry/semantic-conventions': 1.36.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis@0.51.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-router@0.42.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.38.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.36.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-restify@0.43.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-socket.io@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-router@0.42.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-socket.io@0.44.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.36.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-socket.io@0.44.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.16.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.16.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.16.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.36.0 '@types/tedious': 4.0.14 transitivePeerDependencies: @@ -18392,6 +19046,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-tedious@0.33.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-undici@0.14.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18408,6 +19071,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-undici@0.8.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation-winston@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18416,6 +19087,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation-winston@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18425,6 +19104,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.207.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.212.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.212.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18448,6 +19154,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + semver: 7.7.2 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18471,6 +19189,12 @@ snapshots: '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@grpc/grpc-js': 1.13.4 @@ -18487,6 +19211,14 @@ snapshots: '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-proto-exporter-base@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18515,10 +19247,25 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) protobufjs: 7.5.4 + '@opentelemetry/otlp-transformer@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + protobufjs: 7.5.4 + '@opentelemetry/propagation-utils@0.30.16(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/propagation-utils@0.30.16(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/propagator-b3@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18529,6 +19276,11 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18539,10 +19291,17 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common@0.36.2': {} '@opentelemetry/redis-common@0.38.0': {} + '@opentelemetry/redis-common@0.38.3': {} + '@opentelemetry/resource-detector-alibaba-cloud@0.29.7(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18550,6 +19309,13 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-alibaba-cloud@0.29.7(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-aws@1.12.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18557,6 +19323,13 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-aws@1.12.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-azure@0.3.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18564,6 +19337,13 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-azure@0.3.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-container@0.5.3(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18571,6 +19351,13 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-container@0.5.3(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resource-detector-gcp@0.30.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18582,6 +19369,17 @@ snapshots: - encoding - supports-color + '@opentelemetry/resource-detector-gcp@0.30.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.36.0 + gcp-metadata: 6.1.1 + transitivePeerDependencies: + - encoding + - supports-color + '@opentelemetry/resources@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18594,18 +19392,36 @@ snapshots: '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/resources@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/resources@2.7.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-logs@0.46.0(@opentelemetry/api-logs@0.46.0)(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18620,6 +19436,13 @@ snapshots: '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18633,6 +19456,12 @@ snapshots: '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node@0.46.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18674,6 +19503,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/sdk-node@0.55.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.55.0 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/sdk-trace-base@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18688,6 +19539,13 @@ snapshots: '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -18695,6 +19553,13 @@ snapshots: '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-trace-node@1.19.0(@opentelemetry/api@1.7.0)': dependencies: '@opentelemetry/api': 1.7.0 @@ -18715,6 +19580,16 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) semver: 7.7.2 + '@opentelemetry/sdk-trace-node@1.28.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 1.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.1) + semver: 7.7.2 + '@opentelemetry/semantic-conventions@1.19.0': {} '@opentelemetry/semantic-conventions@1.27.0': {} @@ -18723,16 +19598,28 @@ snapshots: '@opentelemetry/semantic-conventions@1.36.0': {} + '@opentelemetry/semantic-conventions@1.40.0': {} + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@oslojs/asn1@1.0.0': dependencies: '@oslojs/binary': 1.0.0 @@ -18915,6 +19802,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@prisma/instrumentation@7.6.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + '@prisma/internals@5.0.0': dependencies: '@antfu/ni': 0.21.4 @@ -19008,33 +19902,6 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@puppeteer/browsers@2.10.8': - dependencies: - debug: 4.4.1 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.2 - tar-fs: 3.1.0 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-buffer - - supports-color - - '@puppeteer/browsers@2.6.1': - dependencies: - debug: 4.4.1 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.2 - tar-fs: 3.1.0 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-buffer - - supports-color - '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -20176,7 +21043,9 @@ snapshots: '@sentry/core@10.5.0': {} - '@sentry/nextjs@10.5.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9))': + '@sentry/core@10.50.0': {} + + '@sentry/nextjs@10.5.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17)))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.36.0 @@ -20187,7 +21056,7 @@ snapshots: '@sentry/opentelemetry': 10.5.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0) '@sentry/react': 10.5.0(react@19.2.3) '@sentry/vercel-edge': 10.5.0 - '@sentry/webpack-plugin': 4.1.1(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)) + '@sentry/webpack-plugin': 4.1.1(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))) chalk: 3.0.0 next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) resolve: 1.22.8 @@ -20215,6 +21084,18 @@ snapshots: '@sentry/opentelemetry': 10.5.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0) import-in-the-middle: 1.14.2 + '@sentry/node-core@10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)': + dependencies: + '@sentry/core': 10.50.0 + '@sentry/opentelemetry': 10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) + import-in-the-middle: 3.0.1 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@sentry/node@10.5.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -20255,6 +21136,43 @@ snapshots: transitivePeerDependencies: - supports-color + '@sentry/node@10.50.0': + dependencies: + '@fastify/otel': 0.18.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-connect': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dataloader': 0.31.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fs': 0.33.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-generic-pool': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-graphql': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-hapi': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-ioredis': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-kafkajs': 0.23.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-knex': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-koa': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-lru-memoizer': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongodb': 0.67.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongoose': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql2': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pg': 0.66.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-redis': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-tedious': 0.33.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@prisma/instrumentation': 7.6.0(@opentelemetry/api@1.9.1) + '@sentry/core': 10.50.0 + '@sentry/node-core': 10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/opentelemetry': 10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) + import-in-the-middle: 3.0.1 + transitivePeerDependencies: + - '@opentelemetry/exporter-trace-otlp-http' + - supports-color + '@sentry/opentelemetry@10.5.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.36.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -20264,6 +21182,14 @@ snapshots: '@opentelemetry/semantic-conventions': 1.36.0 '@sentry/core': 10.5.0 + '@sentry/opentelemetry@10.50.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@sentry/core': 10.50.0 + '@sentry/react@10.5.0(react@19.2.3)': dependencies: '@sentry/browser': 10.5.0 @@ -20277,12 +21203,12 @@ snapshots: '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@sentry/core': 10.5.0 - '@sentry/webpack-plugin@4.1.1(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9))': + '@sentry/webpack-plugin@4.1.1(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17)))': dependencies: '@sentry/bundler-plugin-core': 4.1.1 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9) + webpack: 5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17)) transitivePeerDependencies: - encoding - supports-color @@ -20341,10 +21267,19 @@ snapshots: '@smithy/util-base64': 4.0.0 tslib: 2.8.1 + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + '@smithy/chunked-blob-reader@5.0.0': dependencies: tslib: 2.8.1 + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/config-resolver@4.1.5': dependencies: '@smithy/node-config-provider': 4.1.4 @@ -20353,6 +21288,28 @@ snapshots: '@smithy/util-middleware': 4.0.5 tslib: 2.8.1 + '@smithy/config-resolver@4.4.17': + dependencies: + '@smithy/node-config-provider': 4.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.4.2 + '@smithy/util-middleware': 4.2.14 + tslib: 2.8.1 + + '@smithy/core@3.23.17': + dependencies: + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-stream': 4.5.25 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + '@smithy/core@3.8.0': dependencies: '@smithy/middleware-serde': 4.0.9 @@ -20375,36 +21332,74 @@ snapshots: '@smithy/url-parser': 4.0.5 tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.14': + dependencies: + '@smithy/node-config-provider': 4.3.14 + '@smithy/property-provider': 4.2.14 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + tslib: 2.8.1 + '@smithy/eventstream-codec@4.0.5': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.3.2 + '@smithy/types': 4.14.1 '@smithy/util-hex-encoding': 4.0.0 tslib: 2.8.1 + '@smithy/eventstream-codec@4.2.14': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.1 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.0.5': dependencies: '@smithy/eventstream-serde-universal': 4.0.5 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.2.14': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-config-resolver@4.1.3': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/eventstream-serde-config-resolver@4.3.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-node@4.0.5': dependencies: '@smithy/eventstream-serde-universal': 4.0.5 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/eventstream-serde-node@4.2.14': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/eventstream-serde-universal@4.0.5': dependencies: '@smithy/eventstream-codec': 4.0.5 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/eventstream-serde-universal@4.2.14': + dependencies: + '@smithy/eventstream-codec': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/fetch-http-handler@5.1.1': dependencies: '@smithy/protocol-http': 5.1.3 @@ -20413,6 +21408,14 @@ snapshots: '@smithy/util-base64': 4.0.0 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.3.17': + dependencies: + '@smithy/protocol-http': 5.3.14 + '@smithy/querystring-builder': 4.2.14 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + '@smithy/hash-blob-browser@4.0.5': dependencies: '@smithy/chunked-blob-reader': 5.0.0 @@ -20420,6 +21423,13 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/hash-blob-browser@4.2.15': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/hash-node@4.0.5': dependencies: '@smithy/types': 4.3.2 @@ -20427,17 +21437,35 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/hash-node@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/hash-stream-node@4.0.5': dependencies: '@smithy/types': 4.3.2 '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/hash-stream-node@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/invalid-dependency@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/invalid-dependency@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 @@ -20446,18 +21474,34 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/md5-js@4.0.5': dependencies: '@smithy/types': 4.3.2 '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/md5-js@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/middleware-content-length@4.0.5': dependencies: '@smithy/protocol-http': 5.1.3 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/middleware-content-length@4.2.14': + dependencies: + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/middleware-endpoint@4.1.18': dependencies: '@smithy/core': 3.8.0 @@ -20469,6 +21513,17 @@ snapshots: '@smithy/util-middleware': 4.0.5 tslib: 2.8.1 + '@smithy/middleware-endpoint@4.4.32': + dependencies: + '@smithy/core': 3.23.17 + '@smithy/middleware-serde': 4.2.20 + '@smithy/node-config-provider': 4.3.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + '@smithy/url-parser': 4.2.14 + '@smithy/util-middleware': 4.2.14 + tslib: 2.8.1 + '@smithy/middleware-retry@4.1.19': dependencies: '@smithy/node-config-provider': 4.1.4 @@ -20482,17 +21537,42 @@ snapshots: tslib: 2.8.1 uuid: 9.0.1 + '@smithy/middleware-retry@4.5.7': + dependencies: + '@smithy/core': 3.23.17 + '@smithy/node-config-provider': 4.3.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/service-error-classification': 4.3.1 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-retry': 4.3.6 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + '@smithy/middleware-serde@4.0.9': dependencies: '@smithy/protocol-http': 5.1.3 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/middleware-serde@4.2.20': + dependencies: + '@smithy/core': 3.23.17 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/middleware-stack@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/middleware-stack@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/node-config-provider@4.1.4': dependencies: '@smithy/property-provider': 4.0.5 @@ -20500,6 +21580,13 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/node-config-provider@4.3.14': + dependencies: + '@smithy/property-provider': 4.2.14 + '@smithy/shared-ini-file-loader': 4.4.9 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/node-http-handler@4.1.1': dependencies: '@smithy/abort-controller': 4.0.5 @@ -20508,36 +21595,73 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/node-http-handler@4.6.1': + dependencies: + '@smithy/protocol-http': 5.3.14 + '@smithy/querystring-builder': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/property-provider@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/property-provider@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/protocol-http@5.1.3': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/protocol-http@5.3.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/querystring-builder@4.0.5': dependencies: '@smithy/types': 4.3.2 '@smithy/util-uri-escape': 4.0.0 tslib: 2.8.1 + '@smithy/querystring-builder@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + '@smithy/querystring-parser@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/querystring-parser@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/service-error-classification@4.0.7': dependencies: '@smithy/types': 4.3.2 + '@smithy/service-error-classification@4.3.1': + dependencies: + '@smithy/types': 4.14.1 + '@smithy/shared-ini-file-loader@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/shared-ini-file-loader@4.4.9': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/signature-v4@5.1.3': dependencies: '@smithy/is-array-buffer': 4.0.0 @@ -20549,6 +21673,27 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/signature-v4@5.3.14': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.14 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/smithy-client@4.12.13': + dependencies: + '@smithy/core': 3.23.17 + '@smithy/middleware-endpoint': 4.4.32 + '@smithy/middleware-stack': 4.2.14 + '@smithy/protocol-http': 5.3.14 + '@smithy/types': 4.14.1 + '@smithy/util-stream': 4.5.25 + tslib: 2.8.1 + '@smithy/smithy-client@4.4.10': dependencies: '@smithy/core': 3.8.0 @@ -20559,6 +21704,10 @@ snapshots: '@smithy/util-stream': 4.2.4 tslib: 2.8.1 + '@smithy/types@4.14.1': + dependencies: + tslib: 2.8.1 + '@smithy/types@4.3.2': dependencies: tslib: 2.8.1 @@ -20569,20 +21718,40 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/url-parser@4.2.14': + dependencies: + '@smithy/querystring-parser': 4.2.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-base64@4.0.0': dependencies: '@smithy/util-buffer-from': 4.0.0 '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-body-length-browser@4.0.0': dependencies: tslib: 2.8.1 + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-body-length-node@4.0.0': dependencies: tslib: 2.8.1 + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 @@ -20593,10 +21762,19 @@ snapshots: '@smithy/is-array-buffer': 4.0.0 tslib: 2.8.1 + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + '@smithy/util-config-provider@4.0.0': dependencies: tslib: 2.8.1 + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@4.0.26': dependencies: '@smithy/property-provider': 4.0.5 @@ -20605,6 +21783,13 @@ snapshots: bowser: 2.12.0 tslib: 2.8.1 + '@smithy/util-defaults-mode-browser@4.3.49': + dependencies: + '@smithy/property-provider': 4.2.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-defaults-mode-node@4.0.26': dependencies: '@smithy/config-resolver': 4.1.5 @@ -20615,27 +21800,58 @@ snapshots: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/util-defaults-mode-node@4.2.54': + dependencies: + '@smithy/config-resolver': 4.4.17 + '@smithy/credential-provider-imds': 4.2.14 + '@smithy/node-config-provider': 4.3.14 + '@smithy/property-provider': 4.2.14 + '@smithy/smithy-client': 4.12.13 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-endpoints@3.0.7': dependencies: '@smithy/node-config-provider': 4.1.4 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/util-endpoints@3.4.2': + dependencies: + '@smithy/node-config-provider': 4.3.14 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-hex-encoding@4.0.0': dependencies: tslib: 2.8.1 + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-middleware@4.0.5': dependencies: '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/util-middleware@4.2.14': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-retry@4.0.7': dependencies: '@smithy/service-error-classification': 4.0.7 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/util-retry@4.3.6': + dependencies: + '@smithy/service-error-classification': 4.3.1 + '@smithy/types': 4.14.1 + tslib: 2.8.1 + '@smithy/util-stream@4.2.4': dependencies: '@smithy/fetch-http-handler': 5.1.1 @@ -20647,10 +21863,25 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 + '@smithy/util-stream@4.5.25': + dependencies: + '@smithy/fetch-http-handler': 5.3.17 + '@smithy/node-http-handler': 4.6.1 + '@smithy/types': 4.14.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + '@smithy/util-uri-escape@4.0.0': dependencies: tslib: 2.8.1 + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 @@ -20661,12 +21892,26 @@ snapshots: '@smithy/util-buffer-from': 4.0.0 tslib: 2.8.1 + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + '@smithy/util-waiter@4.0.7': dependencies: '@smithy/abort-controller': 4.0.5 '@smithy/types': 4.3.2 tslib: 2.8.1 + '@smithy/util-waiter@4.3.0': + dependencies: + '@smithy/types': 4.14.1 + tslib: 2.8.1 + + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} '@storybook/addon-docs@9.1.2(@types/react@19.1.10)(storybook@9.1.2(@testing-library/dom@10.4.1)(msw@2.10.5(@types/node@20.19.11)(typescript@5.9.2))(prettier@3.6.2)(vite@6.3.5(@types/node@20.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)))': @@ -21019,13 +22264,6 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.17 - '@tailwindcss/vite@4.1.17(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))': - dependencies: - '@tailwindcss/node': 4.1.17 - '@tailwindcss/oxide': 4.1.17 - tailwindcss: 4.1.17 - vite: 6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - '@tanstack/query-core@5.85.5': {} '@tanstack/query-devtools@5.84.0': {} @@ -21421,10 +22659,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/fontkit@2.0.8': - dependencies: - '@types/node': 22.17.2 - '@types/geojson@7946.0.16': {} '@types/glob@7.2.0': @@ -21550,6 +22784,10 @@ snapshots: dependencies: '@types/pg': 8.15.5 + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.15.6 + '@types/pg@8.15.4': dependencies: '@types/node': 22.17.2 @@ -21562,6 +22800,12 @@ snapshots: pg-protocol: 1.10.3 pg-types: 2.2.0 + '@types/pg@8.15.6': + dependencies: + '@types/node': 22.17.2 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + '@types/pg@8.6.1': dependencies: '@types/node': 22.17.2 @@ -21629,72 +22873,36 @@ snapshots: '@types/uuid@9.0.8': {} - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 22.17.2 - optional: true - - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260119.1': - optional: true - - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260209.1': - optional: true - - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260119.1': - optional: true - - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260209.1': - optional: true - - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260119.1': - optional: true - - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260209.1': - optional: true - - '@typescript/native-preview-linux-arm@7.0.0-dev.20260119.1': - optional: true - - '@typescript/native-preview-linux-arm@7.0.0-dev.20260209.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260119.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260209.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260119.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260209.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260119.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260209.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260430.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260119.1': - optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260119.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260119.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260119.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260119.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260119.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260119.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260119.1 - - '@typescript/native-preview@7.0.0-dev.20260209.1': + '@typescript/native-preview@7.0.0-dev.20260430.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260209.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260209.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260209.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260209.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260209.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260209.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260209.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260430.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260430.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260430.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260430.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260430.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260430.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260430.1 '@typescript/vfs@1.6.1(typescript@5.9.2)': dependencies: @@ -21959,18 +23167,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))': - dependencies: - '@babel/core': 7.28.3 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - transitivePeerDependencies: - - supports-color - '@vitest/browser@3.0.5(@types/node@20.19.11)(playwright@1.53.0)(typescript@5.9.2)(vite@6.3.5(@types/node@20.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 @@ -22087,56 +23283,6 @@ snapshots: loupe: 3.2.0 tinyrainbow: 2.0.0 - '@volar/kit@2.4.23(typescript@5.9.2)': - dependencies: - '@volar/language-service': 2.4.23 - '@volar/typescript': 2.4.23 - typesafe-path: 0.2.2 - typescript: 5.9.2 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - - '@volar/language-core@2.4.23': - dependencies: - '@volar/source-map': 2.4.23 - - '@volar/language-server@2.4.23': - dependencies: - '@volar/language-core': 2.4.23 - '@volar/language-service': 2.4.23 - '@volar/typescript': 2.4.23 - path-browserify: 1.0.1 - request-light: 0.7.0 - vscode-languageserver: 9.0.1 - vscode-languageserver-protocol: 3.17.5 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - - '@volar/language-service@2.4.23': - dependencies: - '@volar/language-core': 2.4.23 - vscode-languageserver-protocol: 3.17.5 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - - '@volar/source-map@2.4.23': {} - - '@volar/typescript@2.4.23': - dependencies: - '@volar/language-core': 2.4.23 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - - '@vscode/emmet-helper@2.11.0': - dependencies: - emmet: 2.4.11 - jsonc-parser: 2.3.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.17.5 - vscode-uri: 3.1.0 - - '@vscode/l10n@0.0.18': {} - '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -22434,122 +23580,6 @@ snapshots: astring@1.9.0: {} - astro-pdf@1.7.2(astro@5.13.5(@types/node@22.17.2)(ioredis@5.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.46.4)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1))(typescript@5.9.2): - dependencies: - '@puppeteer/browsers': 2.10.8 - astro: 5.13.5(@types/node@22.17.2)(ioredis@5.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.46.4)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1) - kleur: 4.1.5 - p-map: 7.0.3 - puppeteer: 23.11.1(typescript@5.9.2) - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - typescript - - utf-8-validate - - astro@5.13.5(@types/node@22.17.2)(ioredis@5.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.46.4)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(yaml@2.8.1): - dependencies: - '@astrojs/compiler': 2.12.2 - '@astrojs/internal-helpers': 0.7.2 - '@astrojs/markdown-remark': 6.3.6 - '@astrojs/telemetry': 3.3.0 - '@capsizecss/unpack': 2.4.0 - '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.2.0(rollup@4.46.4) - acorn: 8.15.0 - aria-query: 5.3.2 - axobject-query: 4.1.0 - boxen: 8.0.1 - ci-info: 4.3.0 - clsx: 2.1.1 - common-ancestor-path: 1.0.1 - cookie: 1.0.2 - cssesc: 3.0.0 - debug: 4.4.1 - deterministic-object-hash: 2.0.2 - devalue: 5.3.2 - diff: 5.2.0 - dlv: 1.1.3 - dset: 3.1.4 - es-module-lexer: 1.7.0 - esbuild: 0.25.9 - estree-walker: 3.0.3 - flattie: 1.1.1 - fontace: 0.3.0 - github-slugger: 2.0.0 - html-escaper: 3.0.3 - http-cache-semantics: 4.2.0 - import-meta-resolve: 4.2.0 - js-yaml: 4.1.0 - kleur: 4.1.5 - magic-string: 0.30.17 - magicast: 0.3.5 - mrmime: 2.0.1 - neotraverse: 0.6.18 - p-limit: 6.2.0 - p-queue: 8.1.0 - package-manager-detector: 1.3.0 - picomatch: 4.0.3 - prompts: 2.4.2 - rehype: 13.0.2 - semver: 7.7.2 - shiki: 3.11.0 - smol-toml: 1.4.2 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tsconfck: 3.1.6(typescript@5.9.2) - ultrahtml: 1.6.0 - unifont: 0.5.2 - unist-util-visit: 5.0.0 - unstorage: 1.17.0(ioredis@5.7.0) - vfile: 6.0.3 - vite: 6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - vitefu: 1.1.1(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) - xxhash-wasm: 1.1.0 - yargs-parser: 21.1.1 - yocto-spinner: 0.2.3 - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.9.2)(zod@3.25.76) - optionalDependencies: - sharp: 0.33.5 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@netlify/blobs' - - '@planetscale/database' - - '@types/node' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - aws4fetch - - db0 - - encoding - - idb-keyval - - ioredis - - jiti - - less - - lightningcss - - rollup - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - typescript - - uploadthing - - yaml - async-function@1.0.0: {} async@3.2.6: {} @@ -22582,8 +23612,6 @@ snapshots: transitivePeerDependencies: - debug - axobject-query@4.1.0: {} - b4a@1.6.7: {} babel-plugin-macros@3.1.0: @@ -22620,33 +23648,11 @@ snapshots: balanced-match@1.0.2: {} - bare-events@2.6.1: - optional: true - - bare-fs@4.2.2: - dependencies: - bare-events: 2.6.1 - bare-path: 3.0.0 - bare-stream: 2.7.0(bare-events@2.6.1) - optional: true - - bare-os@3.6.2: - optional: true - - bare-path@3.0.0: - dependencies: - bare-os: 3.6.2 - optional: true + balanced-match@4.0.4: {} - bare-stream@2.7.0(bare-events@2.6.1): - dependencies: - streamx: 2.22.1 - optionalDependencies: - bare-events: 2.6.1 + bare-events@2.6.1: optional: true - base-64@1.0.0: {} - base16@1.0.0: {} base64-js@1.5.1: {} @@ -22674,8 +23680,6 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - blob-to-buffer@1.2.9: {} - bowser@2.12.0: {} boxen@7.0.0: @@ -22689,17 +23693,6 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - boxen@8.0.1: - dependencies: - ansi-align: 3.0.1 - camelcase: 8.0.0 - chalk: 5.6.0 - cli-boxes: 3.0.0 - string-width: 7.2.0 - type-fest: 4.41.0 - widest-line: 5.0.0 - wrap-ansi: 9.0.0 - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -22709,13 +23702,13 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@3.0.3: + brace-expansion@5.0.5: dependencies: - fill-range: 7.1.1 + balanced-match: 4.0.4 - brotli@1.3.3: + braces@3.0.3: dependencies: - base64-js: 1.5.1 + fill-range: 7.1.1 browserslist@4.25.3: dependencies: @@ -22777,8 +23770,6 @@ snapshots: camelcase@7.0.1: {} - camelcase@8.0.0: {} - caniuse-lite@1.0.30001735: {} case-anything@2.1.13: {} @@ -22906,20 +23897,14 @@ snapshots: chrome-trace-event@1.0.4: {} - chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): - dependencies: - devtools-protocol: 0.0.1367902 - mitt: 3.0.1 - zod: 3.23.8 - ci-info@3.8.0: {} ci-info@3.9.0: {} - ci-info@4.3.0: {} - cjs-module-lexer@1.4.3: {} + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -22982,8 +23967,6 @@ snapshots: clone@1.0.4: {} - clone@2.1.2: {} - clsx@2.1.1: {} cluster-key-slot@1.1.2: {} @@ -23056,8 +24039,6 @@ snapshots: commander@8.3.0: {} - common-ancestor-path@1.0.1: {} - commondir@1.0.1: {} compress-commons@4.1.2: @@ -23126,12 +24107,8 @@ snapshots: convert-source-map@2.0.0: {} - cookie-es@1.2.2: {} - cookie@0.7.2: {} - cookie@1.0.2: {} - core-js-compat@3.45.0: dependencies: browserslist: 4.25.3 @@ -23170,15 +24147,6 @@ snapshots: optionalDependencies: typescript: 5.9.2 - cosmiconfig@9.0.0(typescript@5.9.2): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - parse-json: 5.2.0 - optionalDependencies: - typescript: 5.9.2 - crc-32@1.2.2: {} crc32-stream@4.0.3: @@ -23197,12 +24165,6 @@ snapshots: cropperjs@1.6.2: {} - cross-fetch@3.2.0: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -23215,17 +24177,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crossws@0.3.5: - dependencies: - uncrypto: 0.1.3 - crypto-random-string@2.0.0: {} - css-tree@3.1.0: - dependencies: - mdn-data: 2.12.2 - source-map-js: 1.2.1 - css.escape@1.5.1: {} cssesc@3.0.0: {} @@ -23512,8 +24465,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.4: {} - degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -23552,42 +24503,26 @@ snapshots: dequal@2.0.3: {} - destr@2.0.5: {} - detect-indent@6.1.0: {} detect-libc@2.0.4: {} detect-node-es@1.1.0: {} - deterministic-object-hash@2.0.2: - dependencies: - base-64: 1.0.0 - - devalue@5.3.2: {} - devlop@1.1.0: dependencies: dequal: 2.0.3 - devtools-protocol@0.0.1367902: {} - - dfa@1.2.0: {} - diacritics@1.3.0: {} diff-match-patch@1.0.5: {} diff@4.0.2: {} - diff@5.2.0: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dlv@1.1.3: {} - doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -23644,8 +24579,6 @@ snapshots: dotenv@16.6.1: {} - dset@3.1.4: {} - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -23660,13 +24593,6 @@ snapshots: electron-to-chromium@1.5.207: {} - emmet@2.4.11: - dependencies: - '@emmetio/abbreviation': 2.3.3 - '@emmetio/css-abbreviation': 2.1.8 - - emoji-regex@10.5.0: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -24023,16 +24949,6 @@ snapshots: - supports-color - tedious - extract-zip@2.0.1: - dependencies: - debug: 4.4.1 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - fast-copy@3.0.2: {} fast-copy@4.0.2: {} @@ -24063,21 +24979,28 @@ snapshots: fast-write-atomic@0.2.1: {} + fast-xml-builder@1.1.5: + dependencies: + path-expression-matcher: 1.5.0 + fast-xml-parser@5.2.5: dependencies: strnum: 2.1.1 + fast-xml-parser@5.7.2: + dependencies: + '@nodable/entities': 2.1.0 + fast-xml-builder: 1.1.5 + path-expression-matcher: 1.5.0 + strnum: 2.2.3 + fastq@1.19.1: dependencies: reusify: 1.1.0 fault@2.0.1: dependencies: - format: 0.2.2 - - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 + format: 0.2.2 fdir@6.5.0(picomatch@4.0.3): optionalDependencies: @@ -24117,27 +25040,8 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 - flattie@1.1.1: {} - follow-redirects@1.15.11: {} - fontace@0.3.0: - dependencies: - '@types/fontkit': 2.0.8 - fontkit: 2.0.4 - - fontkit@2.0.4: - dependencies: - '@swc/helpers': 0.5.17 - brotli: 1.3.3 - clone: 2.1.2 - dfa: 1.2.0 - fast-deep-equal: 3.1.3 - restructure: 3.0.2 - tiny-inflate: 1.0.3 - unicode-properties: 1.4.1 - unicode-trie: 2.0.0 - for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -24240,8 +25144,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.3.1: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -24264,10 +25166,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@5.2.0: - dependencies: - pump: 3.0.3 - get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -24468,18 +25366,6 @@ snapshots: - encoding - supports-color - h3@1.15.4: - dependencies: - cookie-es: 1.2.2 - crossws: 0.3.5 - defu: 6.1.4 - destr: 2.0.5 - iron-webcrypto: 1.2.1 - node-mock-http: 1.0.2 - radix3: 1.1.2 - ufo: 1.6.1 - uncrypto: 0.1.3 - hachure-fill@0.5.2: {} handlebars@4.7.8: @@ -24767,8 +25653,6 @@ snapshots: html-escaper@2.0.2: {} - html-escaper@3.0.3: {} - html-to-text@9.0.5: dependencies: '@selderee/plugin-htmlparser2': 0.11.0 @@ -24794,8 +25678,6 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 - http-cache-semantics@4.2.0: {} - http-proxy-agent@7.0.0: dependencies: agent-base: 7.1.4 @@ -24873,7 +25755,12 @@ snapshots: cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 - import-meta-resolve@4.2.0: {} + import-in-the-middle@3.0.1: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 indent-string@4.0.0: {} @@ -24958,8 +25845,6 @@ snapshots: ip-address@10.0.1: {} - iron-webcrypto@1.2.1: {} - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -25292,10 +26177,6 @@ snapshots: jsonata@2.1.0: {} - jsonc-parser@2.3.1: {} - - jsonc-parser@3.3.1: {} - jsondiffpatch@0.4.1: dependencies: chalk: 2.4.2 @@ -25762,12 +26643,6 @@ snapshots: marked: 7.0.4 react: 19.2.3 - mdast-util-definitions@6.0.0: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - unist-util-visit: 5.0.0 - mdast-util-directive@3.1.0: dependencies: '@types/mdast': 4.0.4 @@ -25968,8 +26843,6 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - mdn-data@2.12.2: {} - mdurl@2.0.0: {} memfs-browser@3.5.10302: @@ -26344,6 +27217,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -26457,8 +27334,6 @@ snapshots: mudder@2.1.1: {} - muggle-string@0.4.1: {} - mute-stream@0.0.8: {} mute-stream@2.0.0: {} @@ -26475,8 +27350,6 @@ snapshots: neo-async@2.6.2: {} - neotraverse@0.6.18: {} - netmask@2.0.2: {} new-github-issue-url@0.2.1: {} @@ -26491,7 +27364,7 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - next@15.5.9(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@15.5.9(@babel/core@7.24.5)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 @@ -26509,7 +27382,7 @@ snapshots: '@next/swc-linux-x64-musl': 15.5.7 '@next/swc-win32-arm64-msvc': 15.5.7 '@next/swc-win32-x64-msvc': 15.5.7 - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@playwright/test': 1.53.0 sharp: 0.34.3 transitivePeerDependencies: @@ -26541,13 +27414,38 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.3.0(@types/react@19.1.10)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)): + next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 15.5.9 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001735 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.28.3)(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.7 + '@next/swc-darwin-x64': 15.5.7 + '@next/swc-linux-arm64-gnu': 15.5.7 + '@next/swc-linux-arm64-musl': 15.5.7 + '@next/swc-linux-x64-gnu': 15.5.7 + '@next/swc-linux-x64-musl': 15.5.7 + '@next/swc-win32-arm64-msvc': 15.5.7 + '@next/swc-win32-x64-msvc': 15.5.7 + '@opentelemetry/api': 1.9.1 + '@playwright/test': 1.53.0 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nextra-theme-docs@4.3.0(@types/react@19.1.10)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)): dependencies: '@headlessui/react': 2.2.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 - next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-themes: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - nextra: 4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2) + nextra: 4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2) react: 19.2.3 react-compiler-runtime: 19.1.0-rc.2(react@19.2.3) react-dom: 19.2.3(react@19.2.3) @@ -26559,7 +27457,7 @@ snapshots: - immer - use-sync-external-store - nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2): + nextra@4.3.0(acorn@8.15.0)(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.2): dependencies: '@formatjs/intl-localematcher': 0.6.1 '@headlessui/react': 2.2.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -26580,7 +27478,7 @@ snapshots: mdast-util-gfm: 3.1.0 mdast-util-to-hast: 13.2.0 negotiator: 1.0.0 - next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-compiler-runtime: 19.1.0-rc.2(react@19.2.3) react-dom: 19.2.3(react@19.2.3) @@ -26616,8 +27514,6 @@ snapshots: dependencies: lower-case: 1.1.4 - node-fetch-native@1.6.7: {} - node-fetch@2.6.11: dependencies: whatwg-url: 5.0.0 @@ -26630,8 +27526,6 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-mock-http@1.0.2: {} - node-plop@0.26.3: dependencies: '@babel/runtime-corejs3': 7.28.3 @@ -26704,6 +27598,13 @@ snapshots: optionalDependencies: next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + nuqs@2.4.3(next@15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3): + dependencies: + mitt: 3.0.1 + react: 19.2.3 + optionalDependencies: + next: 15.5.9(@babel/core@7.28.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + nwsapi@2.2.21: {} object-assign@4.1.1: {} @@ -26729,14 +27630,6 @@ snapshots: dependencies: isobject: 3.0.1 - ofetch@1.4.1: - dependencies: - destr: 2.0.5 - node-fetch-native: 1.6.7 - ufo: 1.6.1 - - ohash@2.0.11: {} - on-exit-leak-free@2.1.2: {} on-headers@1.0.2: {} @@ -26845,10 +27738,6 @@ snapshots: dependencies: yocto-queue: 1.2.1 - p-limit@6.2.0: - dependencies: - yocto-queue: 1.2.1 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -26923,8 +27812,6 @@ snapshots: '@pagefind/linux-x64': 1.3.0 '@pagefind/windows-x64': 1.3.0 - pako@0.2.9: {} - param-case@2.1.1: dependencies: no-case: 2.3.2 @@ -27002,6 +27889,8 @@ snapshots: path-exists@5.0.0: {} + path-expression-matcher@1.5.0: {} + path-is-absolute@1.0.1: {} path-is-inside@1.0.2: {} @@ -27034,8 +27923,6 @@ snapshots: peberminta@0.9.0: {} - pend@1.2.0: {} - pg-cloudflare@1.2.7: optional: true @@ -27222,16 +28109,6 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-astro@0.14.1: - dependencies: - '@astrojs/compiler': 2.12.2 - prettier: 3.6.2 - sass-formatter: 0.7.9 - optional: true - - prettier@2.8.7: - optional: true - prettier@3.6.2: {} pretty-bytes@6.1.1: {} @@ -27470,35 +28347,6 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@23.11.1: - dependencies: - '@puppeteer/browsers': 2.6.1 - chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) - debug: 4.4.1 - devtools-protocol: 0.0.1367902 - typed-query-selector: 2.12.0 - ws: 8.18.3 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - utf-8-validate - - puppeteer@23.11.1(typescript@5.9.2): - dependencies: - '@puppeteer/browsers': 2.6.1 - chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) - cosmiconfig: 9.0.0(typescript@5.9.2) - devtools-protocol: 0.0.1367902 - puppeteer-core: 23.11.1 - typed-query-selector: 2.12.0 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - typescript - - utf-8-validate - qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -27513,8 +28361,6 @@ snapshots: quick-lru@5.1.1: {} - radix3@1.1.2: {} - ramda@0.30.1: {} ramda@0.31.3: {} @@ -27597,7 +28443,7 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 - react-email@3.0.4(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-email@3.0.4(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/core': 7.24.5 '@babel/parser': 7.24.5 @@ -27609,7 +28455,7 @@ snapshots: glob: 10.5.0 log-symbols: 4.1.0 mime-types: 2.1.35 - next: 15.5.9(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 15.5.9(@babel/core@7.24.5)(@opentelemetry/api@1.9.1)(@playwright/test@1.53.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) normalize-path: 3.0.0 ora: 5.4.1 socket.io: 4.8.0 @@ -28075,10 +28921,6 @@ snapshots: replace-string@3.1.0: {} - request-light@0.5.8: {} - - request-light@0.7.0: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -28091,6 +28933,13 @@ snapshots: transitivePeerDependencies: - supports-color + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.1 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + requires-port@1.0.0: {} resolve-from@4.0.0: {} @@ -28127,8 +28976,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - restructure@3.0.2: {} - retext-latin@4.0.0: dependencies: '@types/nlcst': 2.0.3 @@ -28230,9 +29077,6 @@ snapshots: dependencies: tslib: 2.8.1 - s.color@0.0.15: - optional: true - safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -28260,11 +29104,6 @@ snapshots: safer-buffer@2.1.2: {} - sass-formatter@0.7.9: - dependencies: - suf-log: 2.5.3 - optional: true - saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -28372,33 +29211,6 @@ snapshots: shallow-equal@3.1.0: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.0.4 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - optional: true - sharp@0.34.3: dependencies: color: 4.2.3 @@ -28513,8 +29325,6 @@ snapshots: smart-buffer@4.2.0: {} - smol-toml@1.4.2: {} - snake-case@2.1.0: dependencies: no-case: 2.3.2 @@ -28685,12 +29495,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string-width@7.2.0: - dependencies: - emoji-regex: 10.5.0 - get-east-asian-width: 1.3.1 - strip-ansi: 7.1.0 - string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -28761,6 +29565,8 @@ snapshots: strnum@2.1.1: {} + strnum@2.2.3: {} + style-mod@4.1.2: {} style-to-js@1.1.17: @@ -28796,11 +29602,6 @@ snapshots: stylis@4.3.6: {} - suf-log@2.5.3: - dependencies: - s.color: 0.0.15 - optional: true - supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -28845,16 +29646,6 @@ snapshots: tapable@2.2.2: {} - tar-fs@3.1.0: - dependencies: - pump: 3.0.3 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 4.2.2 - bare-path: 3.0.0 - transitivePeerDependencies: - - bare-buffer - tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -28905,17 +29696,16 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)): + terser-webpack-plugin@5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.17))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))): dependencies: '@jridgewell/trace-mapping': 0.3.30 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.43.1 - webpack: 5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9) + webpack: 5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17)) optionalDependencies: '@swc/core': 1.7.24(@swc/helpers@0.5.17) - esbuild: 0.25.9 terser@5.43.1: dependencies: @@ -28944,8 +29734,6 @@ snapshots: tildify@2.0.0: {} - tiny-inflate@1.0.3: {} - tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -29202,8 +29990,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typed-query-selector@2.12.0: {} - typedarray.prototype.slice@1.0.5: dependencies: call-bind: 1.0.8 @@ -29217,12 +30003,6 @@ snapshots: typedarray@0.0.6: {} - typesafe-path@0.2.2: {} - - typescript-auto-import-cache@0.3.6: - dependencies: - semver: 7.7.2 - typescript@5.9.2: {} uc.micro@2.1.0: {} @@ -29232,8 +30012,6 @@ snapshots: uglify-js@3.19.3: optional: true - ultrahtml@1.6.0: {} - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -29241,13 +30019,6 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 - - uncrypto@0.1.3: {} - undici-types@6.21.0: {} undici-types@7.10.0: {} @@ -29261,18 +30032,8 @@ snapshots: unicode-match-property-value-ecmascript@2.2.0: {} - unicode-properties@1.4.1: - dependencies: - base64-js: 1.5.1 - unicode-trie: 2.0.0 - unicode-property-aliases-ecmascript@2.1.0: {} - unicode-trie@2.0.0: - dependencies: - pako: 0.2.9 - tiny-inflate: 1.0.3 - unicorn-magic@0.1.0: {} unified@11.0.5: @@ -29285,12 +30046,6 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unifont@0.5.2: - dependencies: - css-tree: 3.1.0 - ofetch: 1.4.1 - ohash: 2.0.11 - unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -29384,19 +30139,6 @@ snapshots: acorn: 8.15.0 webpack-virtual-modules: 0.6.2 - unstorage@1.17.0(ioredis@5.7.0): - dependencies: - anymatch: 3.1.3 - chokidar: 4.0.3 - destr: 2.0.5 - h3: 1.15.4 - lru-cache: 10.4.3 - node-fetch-native: 1.6.7 - ofetch: 1.4.1 - ufo: 1.6.1 - optionalDependencies: - ioredis: 5.7.0 - update-browserslist-db@1.1.3(browserslist@4.25.3): dependencies: browserslist: 4.25.3 @@ -29585,23 +30327,6 @@ snapshots: tsx: 4.20.5 yaml: 2.8.1 - vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.46.4 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.17.2 - fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.30.2 - terser: 5.43.1 - tsx: 4.20.5 - yaml: 2.8.1 - vite@6.3.5(@types/node@24.3.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: esbuild: 0.25.9 @@ -29619,10 +30344,6 @@ snapshots: tsx: 4.20.5 yaml: 2.8.1 - vitefu@1.1.1(vite@6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)): - optionalDependencies: - vite: 6.3.5(@types/node@22.17.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.11)(@vitest/browser@3.0.5)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.30.2)(msw@2.10.5(@types/node@20.19.11)(typescript@5.9.2))(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 @@ -29710,93 +30431,8 @@ snapshots: - tsx - yaml - volar-service-css@0.0.62(@volar/language-service@2.4.23): - dependencies: - vscode-css-languageservice: 6.3.7 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - volar-service-emmet@0.0.62(@volar/language-service@2.4.23): - dependencies: - '@emmetio/css-parser': 0.4.0 - '@emmetio/html-matcher': 1.3.0 - '@vscode/emmet-helper': 2.11.0 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - volar-service-html@0.0.62(@volar/language-service@2.4.23): - dependencies: - vscode-html-languageservice: 5.5.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - volar-service-prettier@0.0.62(@volar/language-service@2.4.23)(prettier@3.6.2): - dependencies: - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - prettier: 3.6.2 - - volar-service-typescript-twoslash-queries@0.0.62(@volar/language-service@2.4.23): - dependencies: - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - volar-service-typescript@0.0.62(@volar/language-service@2.4.23): - dependencies: - path-browserify: 1.0.1 - semver: 7.7.2 - typescript-auto-import-cache: 0.3.6 - vscode-languageserver-textdocument: 1.0.12 - vscode-nls: 5.2.0 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - volar-service-yaml@0.0.62(@volar/language-service@2.4.23): - dependencies: - vscode-uri: 3.1.0 - yaml-language-server: 1.15.0 - optionalDependencies: - '@volar/language-service': 2.4.23 - - vscode-css-languageservice@6.3.7: - dependencies: - '@vscode/l10n': 0.0.18 - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.17.5 - vscode-uri: 3.1.0 - - vscode-html-languageservice@5.5.1: - dependencies: - '@vscode/l10n': 0.0.18 - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.17.5 - vscode-uri: 3.1.0 - - vscode-json-languageservice@4.1.8: - dependencies: - jsonc-parser: 3.3.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.17.5 - vscode-nls: 5.2.0 - vscode-uri: 3.1.0 - - vscode-jsonrpc@6.0.0: {} - vscode-jsonrpc@8.2.0: {} - vscode-languageserver-protocol@3.16.0: - dependencies: - vscode-jsonrpc: 6.0.0 - vscode-languageserver-types: 3.16.0 - vscode-languageserver-protocol@3.17.5: dependencies: vscode-jsonrpc: 8.2.0 @@ -29804,24 +30440,14 @@ snapshots: vscode-languageserver-textdocument@1.0.12: {} - vscode-languageserver-types@3.16.0: {} - vscode-languageserver-types@3.17.5: {} - vscode-languageserver@7.0.0: - dependencies: - vscode-languageserver-protocol: 3.16.0 - vscode-languageserver@9.0.1: dependencies: vscode-languageserver-protocol: 3.17.5 - vscode-nls@5.2.0: {} - vscode-uri@3.0.8: {} - vscode-uri@3.1.0: {} - w3c-keyname@2.2.8: {} w3c-xmlserializer@5.0.0: @@ -29849,7 +30475,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9): + webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17)): dependencies: '@types/estree': 1.0.8 '@webassemblyjs/ast': 1.14.1 @@ -29871,7 +30497,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))(esbuild@0.25.9)) + terser-webpack-plugin: 5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.17))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.17))) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -29926,8 +30552,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-pm-runs@1.1.0: {} - which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -29953,10 +30577,6 @@ snapshots: dependencies: string-width: 5.1.2 - widest-line@5.0.0: - dependencies: - string-width: 7.2.0 - wildcard@1.1.2: {} word-wrap@1.2.5: {} @@ -29981,12 +30601,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 - wrap-ansi@9.0.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.1.0 - wrappy@1.0.2: {} ws@8.17.1: {} @@ -29999,8 +30613,6 @@ snapshots: xtend@4.0.2: {} - xxhash-wasm@1.1.0: {} - y18n@5.0.8: {} yallist@3.1.1: {} @@ -30009,25 +30621,8 @@ snapshots: yallist@5.0.0: {} - yaml-language-server@1.15.0: - dependencies: - ajv: 8.17.1 - lodash: 4.17.21 - request-light: 0.5.8 - vscode-json-languageservice: 4.1.8 - vscode-languageserver: 7.0.0 - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.17.5 - vscode-nls: 5.2.0 - vscode-uri: 3.1.0 - yaml: 2.2.2 - optionalDependencies: - prettier: 2.8.7 - yaml@1.10.2: {} - yaml@2.2.2: {} - yaml@2.3.1: {} yaml@2.8.1: {} @@ -30044,11 +30639,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - yjs@13.6.19: dependencies: lib0: 0.2.114 @@ -30059,14 +30649,8 @@ snapshots: yocto-queue@1.2.1: {} - yocto-spinner@0.2.3: - dependencies: - yoctocolors: 2.1.2 - yoctocolors-cjs@2.1.2: {} - yoctocolors@2.1.2: {} - zip-stream@4.1.1: dependencies: archiver-utils: 3.0.4 @@ -30079,21 +30663,10 @@ snapshots: compress-commons: 5.0.3 readable-stream: 3.6.2 - zod-to-json-schema@3.24.6(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod-to-ts@1.2.0(typescript@5.9.2)(zod@3.25.76): - dependencies: - typescript: 5.9.2 - zod: 3.25.76 - zod-validation-error@5.0.0(zod@3.25.76): dependencies: zod: 3.25.76 - zod@3.23.8: {} - zod@3.25.76: {} zod@4.0.0-beta.20250424T163858: diff --git a/self-host/.env.example b/self-host/.env.example index e8faa80fd0..0b3809678f 100644 --- a/self-host/.env.example +++ b/self-host/.env.example @@ -1,7 +1,7 @@ # the default url of the platform -PUBPUB_URL=http://localhost:3000 # the url of the platform +PUBSTAR_URL=http://localhost:3000 # the url of the platform # change this to eg -# PUBPUB_URL=https://platform.example.com +# PUBSTAR_URL=https://platform.example.com # for a production environment @@ -17,24 +17,37 @@ POSTGRES_PORT=5432 # don't forget to update the port in docker-compose.yml if yo MINIO_ROOT_USER= # change this! this is the username for your file server! MINIO_ROOT_PASSWORD= # change this! this is the password for your file server! -ASSETS_BUCKET_NAME=assets -ASSETS_UPLOAD_KEY= # change this! example: asset-user -ASSETS_UPLOAD_SECRET_KEY= # change this! -ASSETS_REGION=us-east-1 # leave this unchanged, unless you are hosting files on a different region on actual AWS +S3_BUCKET_NAME=assets +S3_ACCESS_KEY= # change this! example: asset-user +S3_SECRET_KEY= # change this! +S3_REGION=us-east-1 # leave this unchanged, unless you are hosting files on a different region on actual AWS + +# private backup storage config with separate credentials +S3_BACKUP_BUCKET=backups +S3_BACKUP_ACCESS_KEY= # change this! example: backup-user +S3_BACKUP_SECRET_KEY= # change this! +S3_BACKUP_REGION=us-east-1 +S3_BACKUP_ENDPOINT="http://localhost:9000" +S3_BACKUP_KEY_PREFIX=pg-backups # this is the default value but you ideally should set this up more nicely using our caddy service -ASSETS_STORAGE_ENDPOINT="http://localhost:9000" +# this endpoint is used for signed uploads and server-side s3 calls +S3_ENDPOINT="http://localhost:9000" # you could also set this to the secured endpoint of your file server -# ASSETS_STORAGE_ENDPOINT="https://example.com/assets" +# S3_ENDPOINT="https://example.com/assets" +# optional public endpoint for generated asset urls +# S3_PUBLIC_ENDPOINT="https://assets.example.com" +# if hostname matches S3_BUCKET_NAME => / +# otherwise => // -MAILGUN_SMTP_HOST=localhost -MAILGUN_SMTP_PORT=54325 -MAILGUN_SMTP_PASSWORD="xxx" -MAILGUN_SMTP_USERNAME="xxx" +SMTP_HOST=localhost +SMTP_PORT=54325 +SMTP_PASSWORD="xxx" +SMTP_USERNAME="xxx" API_KEY="super_secret_key" -OTEL_SERVICE_NAME="pubpub-v7-dev" # should be shared across components but not environments +OTEL_SERVICE_NAME="pubstar-v7-dev" # should be shared across components but not environments HONEYCOMB_API_KEY="xxx" # KYSELY_DEBUG="true" diff --git a/self-host/README.md b/self-host/README.md index 60537c378a..6a1666aee4 100644 --- a/self-host/README.md +++ b/self-host/README.md @@ -92,11 +92,11 @@ The hosted version of Platfrom uses AWS S3 to host files. When self-hosting, you If you want to use your own S3-compatible storage service, you will need to set the following environment variables: ```sh -ASSETS_BUCKET_NAME="your-bucket-name" -ASSETS_UPLOAD_KEY="your-access-key" -ASSETS_UPLOAD_SECRET_KEY="your-secret-key" -ASSETS_REGION="your-region" -ASSETS_STORAGE_ENDPOINT="your-storage-endpoint" # only necessary if you are using non-AWS S3-compatible storage service +S3_BUCKET_NAME="your-bucket-name" +S3_ACCESS_KEY="your-access-key" +S3_SECRET_KEY="your-secret-key" +S3_REGION="your-region" +S3_ENDPOINT="your-storage-endpoint" # only necessary if you are using non-AWS S3-compatible storage service ``` You should also remove the `minio` and `minio-init` services from the `docker-compose.yml` file. @@ -123,7 +123,7 @@ openssl rand -base64 32 [System.Web.Security.Membership]::GeneratePassword(32,8) ``` -Run one of these commands twice, and use one for `MINIO_ROOT_PASSWORD` and one for `ASSETS_UPLOAD_SECRET_KEY`. +Run one of these commands twice, and use one for `MINIO_ROOT_PASSWORD` and one for `S3_SECRET_KEY`. ```sh # not needed if you're using a remote file server like AWS S3 @@ -131,10 +131,10 @@ MINIO_ROOT_USER= # change this! this is the username for your file server! MINIO_ROOT_PASSWORD= # change this! this is the password for your file server! # these are either the values of an existing S3-compatible storage service, or the values that will be used to create a new MinIO service -ASSETS_BUCKET_NAME= # example: assets -ASSETS_UPLOAD_KEY= # example: asset-user -ASSETS_UPLOAD_SECRET_KEY= # example: a strong secure password -ASSETS_REGION=us-east-1 # leave this unchanged, unless you are hosting files on a different region on actual AWS +S3_BUCKET_NAME= # example: assets +S3_ACCESS_KEY= # example: asset-user +S3_SECRET_KEY= # example: a strong secure password +S3_REGION=us-east-1 # leave this unchanged, unless you are hosting files on a different region on actual AWS ``` Then, after running `docker compose up -d`, you should be able to visit the MinIO console at `http://localhost:9001`. @@ -163,12 +163,12 @@ Other providers may likely work as well, but are not tested. To use Mailgun, you will need to create an account on [Mailgun](https://www.mailgun.com/) and set the following environment variables: ```sh -MAILGUN_SMTP_HOST="smtp.mailgun.org" -MAILGUN_SMTP_PORT=587 -MAILGUN_SMTP_USERNAME="postmaster@your-mailgun-domain.mailgun.org" -MAILGUN_SMTP_PASSWORD="your-mailgun-password" -MAILGUN_SMTP_FROM="email@your-mailgun-domain.mailgun.org" -MAILGUN_SMTP_FROM_NAME="Your Organization" +SMTP_HOST="smtp.mailgun.org" +SMTP_PORT=587 +SMTP_USERNAME="postmaster@your-mailgun-domain.mailgun.org" +SMTP_PASSWORD="your-mailgun-password" +SMTP_FROM="email@your-mailgun-domain.mailgun.org" +SMTP_FROM_NAME="Your Organization" ``` ##### Gmail @@ -178,24 +178,24 @@ To use Gmail to relay emails through PubPub, you will need to create an [app pas You will be limited to 2000 emails per day by default this way. ```sh -MAILGUN_SMTP_HOST="smtp.gmail.com" -MAILGUN_SMTP_PORT=587 # or 465 for SSL -MAILGUN_SMTP_USERNAME="email@gmail.com" -MAILGUN_SMTP_PASSWORD="your app password" # this will be a 16 character string -MAILGUN_SMTP_FROM="email@gmail.com" # technically optional, but you will almost definitely need to set this. -MAILGUN_SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" +SMTP_HOST="smtp.gmail.com" +SMTP_PORT=587 # or 465 for SSL +SMTP_USERNAME="email@gmail.com" +SMTP_PASSWORD="your app password" # this will be a 16 character string +SMTP_FROM="email@gmail.com" # technically optional, but you will almost definitely need to set this. +SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" ``` If you need a higher limit of 10,000 emails, you can use the SMTP relay service. This will require extra configuration however: https://support.google.com/a/answer/176600?hl=en ```sh -MAILGUN_SMTP_HOST="smtp-relay.gmail.com" -MAILGUN_SMTP_PORT=587 # or 465 for SSL -MAILGUN_SMTP_USERNAME="email@gmail.com" -MAILGUN_SMTP_PASSWORD="your app password" # this will be a 16 character string -MAILGUN_SMTP_FROM="email@gmail.com" # technically optional, but you will almost definitely need to set this. -MAILGUN_SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" +SMTP_HOST="smtp-relay.gmail.com" +SMTP_PORT=587 # or 465 for SSL +SMTP_USERNAME="email@gmail.com" +SMTP_PASSWORD="your app password" # this will be a 16 character string +SMTP_FROM="email@gmail.com" # technically optional, but you will almost definitely need to set this. +SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" ``` ##### Office 365 @@ -205,12 +205,12 @@ You can (for now) send emails through Office 365 Outlook/Exchange through SMTP, You cannot send emails through shared mailboxes, you will need to an existing Microsoft account with a valid Office 365 subscription. ```sh -MAILGUN_SMTP_HOST="smtp.office365.com" -MAILGUN_SMTP_PORT=587 -MAILGUN_SMTP_USERNAME="email@outlook.com" -MAILGUN_SMTP_PASSWORD="your-password" -MAILGUN_SMTP_FROM="email@outlook.com" # technically optional, but you will almost definitely need to set this, as it will use `hello@pubpub.org` by default. -MAILGUN_SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" +SMTP_HOST="smtp.office365.com" +SMTP_PORT=587 +SMTP_USERNAME="email@outlook.com" +SMTP_PASSWORD="your-password" +SMTP_FROM="email@outlook.com" # technically optional, but you will almost definitely need to set this, as it will use `hello@pubpub.org` by default. +SMTP_FROM_NAME="Your Organization" # Optional, will default to "PubPub Team" ``` ##### No email @@ -235,16 +235,16 @@ You may want to use your own postgres database instead, in which case you can di ```yml db: - condition: service_started + condition: service_started ``` from the `depends_on` section of the `platform` service. ```yml platform: - depends_on: - db: - condition: service_started + depends_on: + db: + condition: service_started ``` #### MinIO diff --git a/self-host/caddy/Caddyfile b/self-host/caddy/Caddyfile index f1389fe552..e8356a3d3a 100644 --- a/self-host/caddy/Caddyfile +++ b/self-host/caddy/Caddyfile @@ -3,9 +3,9 @@ } example.com { - # keep this if you want your files to be accessible at example.com/a* + # keep this if you want your files to be accessible at example.com/assets* # this should instead be a subdomain, like assets.example.com - handle_path /a* { + handle_path /assets* { reverse_proxy minio:9000 } @@ -18,16 +18,25 @@ example.com { } # serve static sites from s3/minio - # requires caddy built with s3fs module (see Dockerfile.caddy) + # rewrite maps to minio path-style: /{bucket}/sites/{community}/{subpath}/... handle_path /sites/* { - root * /sites - file_server { - fs s3 { - bucket {$ASSETS_BUCKET_NAME:assets} - region {$S3_REGION:us-east-1} - endpoint {$S3_ENDPOINT:http://minio:9000} - access_key {$ASSETS_UPLOAD_KEY} - secret_key {$ASSETS_UPLOAD_SECRET_KEY} + @pathWithSlash path_regexp dir (.+)/$ + handle @pathWithSlash { + redir {re.dir.1} permanent + } + + rewrite * /{$S3_BUCKET_NAME:pubstar-assets}/sites{uri} + + reverse_proxy minio:9000 { + @error status 403 404 + handle_response @error { + rewrite * {uri}/index.html + reverse_proxy minio:9000 { + @nestedError status 404 + handle_response @nestedError { + respond "Not found" 404 + } + } } } } @@ -40,7 +49,7 @@ example.com { # if you want to use a different domain for your files, you can do so here # for instance, now all your files will be accessible at assets.example.com -# if you go this route, be sure to update your ASSETS_STORAGE_ENDPOINT in .env and restart your services +# if you go this route, be sure to update your S3_ENDPOINT in .env and restart your services # assets.example.com { # reverse_proxy minio:9000 # } diff --git a/self-host/docker-compose.yml b/self-host/docker-compose.yml index 5249642114..6a41d152b8 100644 --- a/self-host/docker-compose.yml +++ b/self-host/docker-compose.yml @@ -10,46 +10,37 @@ services: condition: service_started platform-jobs: condition: service_started - platform-migrations: - condition: service_completed_successfully minio-init: condition: service_completed_successfully platform: linux/amd64 - image: ghcr.io/pubpub/platform:latest + image: ghcr.io/knowledgefutures/pubplatform:latest env_file: .env environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + S3_BACKUP_REGION: ${S3_BACKUP_REGION:-us-east-1} + S3_BACKUP_ENDPOINT: ${S3_BACKUP_ENDPOINT:-http://minio:9000} + S3_BACKUP_KEY_PREFIX: ${S3_BACKUP_KEY_PREFIX:-pg-backups} ports: - "3000:3000" networks: - app-network - # platfrom jobs service + # platform jobs service # takes care of longer running tasks like scheduling actions platform-jobs: - depends_on: - platform-migrations: - condition: service_completed_successfully - platform: linux/amd64 - image: ghcr.io/pubpub/platform-jobs:latest - env_file: .env - environment: - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} - # change this if you change the platform port - PUBPUB_URL: http://platform:3000 - networks: - - app-network - - platform-migrations: - platform: linux/amd64 depends_on: db: condition: service_started - image: ghcr.io/pubpub/platform-migrations:latest + platform: linux/amd64 + image: ghcr.io/knowledgefutures/pubplatform-jobs:latest env_file: .env environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} - command: ["pnpm", "--filter", "core", "migrate-docker"] + # change this if you change the platform port + PUBSTAR_URL: http://platform:3000 + S3_BACKUP_REGION: ${S3_BACKUP_REGION:-us-east-1} + S3_BACKUP_ENDPOINT: ${S3_BACKUP_ENDPOINT:-http://minio:9000} + S3_BACKUP_KEY_PREFIX: ${S3_BACKUP_KEY_PREFIX:-pg-backups} networks: - app-network @@ -65,7 +56,7 @@ services: # can be removed if you manually set the DATABASE_URL environment variable in .env # to another postgres database you have access to db: - image: postgres:15 + image: postgres:17 restart: always env_file: .env volumes: @@ -79,10 +70,8 @@ services: # can be removed if you manually set up a reverse proxy like nginx instead # useful if you want your assets, platform, and site to be on the same domain # but with different paths - # note: requires the caddy-sites image built from Dockerfile.caddy - # you can build it with: docker build -f Dockerfile.caddy -t ghcr.io/pubpub/caddy-sites:latest . caddy: - image: ghcr.io/pubpub/caddy-sites:latest + image: caddy:latest depends_on: - platform - platform-jobs @@ -105,7 +94,7 @@ services: image: minio/minio:latest env_file: .env healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"] + test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/ready" ] interval: 1m30s timeout: 30s retries: 5 @@ -128,7 +117,7 @@ services: condition: service_healthy image: minio/mc:latest env_file: .env - entrypoint: ["/bin/sh", "/minio-init.sh"] + entrypoint: [ "/bin/sh", "/minio-init.sh" ] volumes: - ./minio-init.sh:/minio-init.sh networks: diff --git a/self-host/minio-init.sh b/self-host/minio-init.sh index 42f08174ce..075ad0c6a1 100644 --- a/self-host/minio-init.sh +++ b/self-host/minio-init.sh @@ -1,5 +1,12 @@ /usr/bin/mc alias set myminio http://minio:9000 "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}"; -/usr/bin/mc mb --ignore-existing myminio/"${ASSETS_BUCKET_NAME}"; -/usr/bin/mc anonymous set download myminio/"${ASSETS_BUCKET_NAME}"; -/usr/bin/mc admin user add myminio "${ASSETS_UPLOAD_KEY}" "${ASSETS_UPLOAD_SECRET_KEY}"; -/usr/bin/mc admin policy attach myminio readwrite --user "${ASSETS_UPLOAD_KEY}"; \ No newline at end of file +/usr/bin/mc mb --ignore-existing myminio/"${S3_BUCKET_NAME}"; +/usr/bin/mc anonymous set download myminio/"${S3_BUCKET_NAME}"; +/usr/bin/mc admin user add myminio "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}"; +/usr/bin/mc admin policy attach myminio readwrite --user "${S3_ACCESS_KEY}"; + +if [ -n "${S3_BACKUP_BUCKET}" ] && [ -n "${S3_BACKUP_ACCESS_KEY}" ] && [ -n "${S3_BACKUP_SECRET_KEY}" ]; then + /usr/bin/mc mb --ignore-existing myminio/"${S3_BACKUP_BUCKET}"; + /usr/bin/mc anonymous set none myminio/"${S3_BACKUP_BUCKET}"; + /usr/bin/mc admin user add myminio "${S3_BACKUP_ACCESS_KEY}" "${S3_BACKUP_SECRET_KEY}"; + /usr/bin/mc admin policy attach myminio readwrite --user "${S3_BACKUP_ACCESS_KEY}"; +fi \ No newline at end of file diff --git a/site-builder-2/.env.development b/site-builder-2/.env.development index 7ec4258e58..856a94ec1f 100644 --- a/site-builder-2/.env.development +++ b/site-builder-2/.env.development @@ -1,5 +1,5 @@ # env vars for sitebuilder -PUBPUB_URL=http://localhost:3000 +PUBSTAR_URL=http://localhost:3000 # change these to the tokens for the community you want to test the site for # this does not need to be a site-building token, it can be any with the correct permissions. # TODO: add a seed that by default actually works with the site diff --git a/site-builder-2/.env.server.development b/site-builder-2/.env.server.development index bcb75ed60c..d6d8a74fad 100644 --- a/site-builder-2/.env.server.development +++ b/site-builder-2/.env.server.development @@ -1,8 +1,8 @@ -PUBPUB_URL=http://localhost:3000 +PUBSTAR_URL=http://localhost:3000 S3_ACCESS_KEY=pubpubuser S3_SECRET_KEY=pubpubpass S3_ENDPOINT=http://localhost:9000 S3_REGION=us-east-1 -S3_BUCKET_NAME=assets.v7.pubpub.org +S3_BUCKET_NAME=assets.pubpub.org S3_REGION=us-east-1 PORT=4000 diff --git a/site-builder-2/docker-compose.yml b/site-builder-2/docker-compose.yml index 84a1762107..0f4ac107bb 100644 --- a/site-builder-2/docker-compose.yml +++ b/site-builder-2/docker-compose.yml @@ -12,7 +12,7 @@ services: - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME:-site-builder} - - S3_PUBLIC_URL=${S3_PUBLIC_URL:-http://localhost:9000} + - S3_PUBLIC_ENDPOINT=${S3_PUBLIC_ENDPOINT:-http://localhost:9000} depends_on: minio: condition: service_healthy diff --git a/site-builder-2/package.json b/site-builder-2/package.json index 965582ee96..7dadb82c6e 100644 --- a/site-builder-2/package.json +++ b/site-builder-2/package.json @@ -20,7 +20,7 @@ "@ts-rest/core": "catalog:", "@ts-rest/serverless": "^3.52.1", "archiver": "^6.0.2", - "@pubpub/json-interpolate": "workspace:", + "@pubstar/json-interpolate": "workspace:", "contracts": "workspace:", "dotenv": "^16.4.5", "hono": "^4.9.7", diff --git a/site-builder-2/server/env.ts b/site-builder-2/server/env.ts index 9bc240e34c..22da1bdc29 100644 --- a/site-builder-2/server/env.ts +++ b/site-builder-2/server/env.ts @@ -3,8 +3,9 @@ import { z } from "zod" export const SERVER_ENV = createEnv({ server: { - PUBPUB_URL: z.string().url(), + PUBSTAR_URL: z.string().url(), S3_ENDPOINT: z.string().url().optional(), + S3_PUBLIC_ENDPOINT: z.string().url().optional(), S3_REGION: z.string(), S3_ACCESS_KEY: z.string(), S3_SECRET_KEY: z.string(), diff --git a/site-builder-2/server/server.ts b/site-builder-2/server/server.ts index 027e8af9b2..6888fa6b5f 100644 --- a/site-builder-2/server/server.ts +++ b/site-builder-2/server/server.ts @@ -14,7 +14,7 @@ import { fetchRequestHandler, tsr } from "@ts-rest/serverless/fetch" import archiver from "archiver" import { Hono } from "hono" -import { interpolate } from "@pubpub/json-interpolate" +import { interpolate } from "@pubstar/json-interpolate" import { createPubProxy, siteApi } from "contracts" import { siteBuilderApi } from "contracts/resources/site-builder-2" import { logger } from "logger" @@ -31,6 +31,49 @@ interface ArchiverError extends Error { let s3Client: S3Client +const trimSlashes = (value: string) => value.replace(/^\/+|\/+$/g, "") + +const trimLeadingSlashes = (value: string) => value.replace(/^\/+/, "") + +const isBucketHost = (hostname: string) => + hostname === SERVER_ENV.S3_BUCKET_NAME || hostname.startsWith(`${SERVER_ENV.S3_BUCKET_NAME}.`) + +const shouldIncludeBucketInPath = (baseUrl: URL) => { + const basePath = trimSlashes(baseUrl.pathname) + const basePathIncludesBucket = + basePath === SERVER_ENV.S3_BUCKET_NAME || + basePath.startsWith(`${SERVER_ENV.S3_BUCKET_NAME}/`) + + if (basePathIncludesBucket) { + return false + } + + return !isBucketHost(baseUrl.hostname) +} + +const buildS3PublicUrl = (key: string) => { + const publicEndpoint = SERVER_ENV.S3_PUBLIC_ENDPOINT || SERVER_ENV.S3_ENDPOINT + const normalizedKey = trimLeadingSlashes(key) + + if (!publicEndpoint) { + return `https://${SERVER_ENV.S3_BUCKET_NAME}.s3.${SERVER_ENV.S3_REGION}.amazonaws.com/${normalizedKey}` + } + + const baseUrl = new URL(publicEndpoint) + const basePath = trimSlashes(baseUrl.pathname) + const shouldIncludeBucket = shouldIncludeBucketInPath(baseUrl) + + const pathSegments = [ + basePath, + shouldIncludeBucket ? SERVER_ENV.S3_BUCKET_NAME : null, + normalizedKey, + ].filter(Boolean) + + baseUrl.pathname = `/${pathSegments.join("/")}` + + return baseUrl.toString() +} + export const getS3Client = () => { if (s3Client) { return s3Client @@ -96,8 +139,9 @@ export const uploadFileToS3 = async ( }) ) - const result = await parallelUploads3.done() - return result.Location! + await parallelUploads3.done() + + return buildS3PublicUrl(key) } const createZipAndUploadToS3 = async ( @@ -272,12 +316,7 @@ const uploadDirectoryToS3 = async ( await uploadRecursive(sourceDir, s3Prefix) const s3FolderPath = s3Prefix - let s3FolderUrl: string - if (SERVER_ENV.S3_ENDPOINT) { - s3FolderUrl = `${SERVER_ENV.S3_ENDPOINT}/${bucket}/${s3Prefix}` - } else { - s3FolderUrl = `https://${bucket}.s3.${SERVER_ENV.S3_REGION}.amazonaws.com/${s3Prefix}` - } + const s3FolderUrl = buildS3PublicUrl(s3Prefix) return { uploadedFiles, s3FolderPath, s3FolderUrl } } @@ -288,7 +327,7 @@ const verifySiteBuilderToken = async (authHeader: string, communitySlug: string) } const client = initClient(siteApi, { - baseUrl: SERVER_ENV.PUBPUB_URL, + baseUrl: SERVER_ENV.PUBSTAR_URL, baseHeaders: { Authorization: authHeader, }, @@ -435,7 +474,7 @@ const renderPageGroup = async ( const context: Record = { pubs: pubProxies, community: communityContext, - env: { PUBPUB_URL: opts.siteUrl }, + env: { PUBSTAR_URL: opts.siteUrl }, } const [slugErr, slug] = await tryCatch(interpolate(group.slugTemplate, context)) const interpolatedSlug = (slugErr ? "index" : slug) as string @@ -462,7 +501,7 @@ const renderPageGroup = async ( const context: Record = { pub: pubProxy, community: communityContext, - env: { PUBPUB_URL: opts.siteUrl }, + env: { PUBSTAR_URL: opts.siteUrl }, } const [slugErr, slug] = await tryCatch(interpolate(group.slugTemplate, context)) if (slugErr) logger.error({ msg: "Error interpolating slug", err: slugErr }) @@ -580,26 +619,29 @@ const router = tsr.router(siteBuilderApi, { // Find the first rendered page for the URL const firstPage = renderedGroups.flatMap((g) => g.pages)[0] - let zipUploadResult: string - let folderUploadResult: { - uploadedFiles: number - s3FolderPath: string - s3FolderUrl: string - } - const zipFileName = `site-${timestamp}.zip` const zipUploadId = "site-archives" - zipUploadResult = await createZipAndUploadToS3(distDir, zipUploadId, zipFileName) + const zipInternalUrl = await createZipAndUploadToS3( + distDir, + zipUploadId, + zipFileName + ) const subpath = body.subpath ?? body.automationRunId const s3Prefix = `sites/${communitySlug}/${subpath}` - folderUploadResult = await uploadDirectoryToS3(distDir, s3Prefix) + const folderUploadResult = await uploadDirectoryToS3(distDir, s3Prefix) + + const publicEndpoint = SERVER_ENV.S3_PUBLIC_ENDPOINT || SERVER_ENV.S3_ENDPOINT + const zipKey = `${zipUploadId}/${zipFileName}` + const zipUrl = publicEndpoint ? buildS3PublicUrl(zipKey) : zipInternalUrl let publicSiteUrl: string | undefined let firstPageUrl: string | undefined + if (SERVER_ENV.SITES_BASE_URL) { const baseUrl = SERVER_ENV.SITES_BASE_URL.replace(/\/$/, "") publicSiteUrl = `${baseUrl}/${communitySlug}/${subpath}/` + if (firstPage) { const pageSlug = firstPage.slug || firstPage.id firstPageUrl = `${publicSiteUrl}${pageSlug}` @@ -613,7 +655,7 @@ const router = tsr.router(siteBuilderApi, { body: { success: true, message: "Site built and uploaded successfully", - url: zipUploadResult, + url: zipUrl, timestamp, s3FolderPath: folderUploadResult.s3FolderPath, s3FolderUrl: folderUploadResult.s3FolderUrl,